import { type AnyZodObject, z } from "zod"; import type { WebhookSendOptions } from "@flowcore/sdk-transformer-core"; import type { FlowcoreEvent } from "../contracts/event.js"; import type { Logger } from "./logger.js"; import type { EventMetadata, PathwayContract, PathwayDeliveryStore, PathwayKey, PathwayState, PathwayWriteOptions, WritablePathway } from "./types.js"; import type { PathwayClusterOptions } from "./cluster/types.js"; import { ClusterManager } from "./cluster/cluster-manager.js"; import type { AutoProvisionConfig, PathwayPumpOptions, PumpState } from "./pump/types.js"; import { PathwayPump } from "./pump/pathway-pump.js"; import { type ProvisionFailureConfig, type ProvisionFailureMode } from "./provisioner.js"; import { type ProvisionRetryConfig } from "./provisioning-execution.js"; export type { AutoProvisionConfig } from "./pump/types.js"; import { FileEventSchema, FileInputSchema } from "./types.js"; import { type PathwayEncryptionConfig } from "./encryption.js"; import { type PathwayChunkingConfig } from "./chunking.js"; import type { PathwayChunkStore } from "./types.js"; /** * Defines the mode for auditing pathway operations * - "user": Normal user-initiated operations * - "system": System-initiated operations on behalf of a user */ export type AuditMode = "user" | "system"; /** * Handler function for auditing pathway events * @param path The pathway path being audited * @param event The event data being processed */ export type AuditHandler = (path: string, event: FlowcoreEvent) => void; /** * Represents an entity that can be used for audit purposes * @property entityId The unique identifier for the entity * @property entityType The type of entity (e.g., "user" or "key") */ export type UserResolverEntity = { entityId: string; entityType: "user" | "key"; }; /** * Async function that resolves to the current user ID * Used for audit functionality to track which user initiated an action */ export type UserIdResolver = () => Promise | UserResolverEntity; /** * Extended webhook send options with additional audit-specific options */ export interface AuditWebhookSendOptions extends WebhookSendOptions { /** * Custom HTTP headers to include with the webhook request */ headers?: Record; } /** * Represents the set log level for different internal log messages * @property debug Log level for debug messages * @property info Log level for info messages * @property warn Log level for warn messages * @property error Log level for error messages */ export type LogLevel = keyof Pick; /** * Configuration for log levels used by PathwaysBuilder for various operations. * * @property writeSuccess Log level used when a write operation is successful. Defaults to 'info'. * @property pulseSuccess Log level used when a pulse is successfully sent. Defaults to 'debug'. * @property pulseFailure Log level used when a pulse emission fails. Defaults to 'warn'. * @property provisionSuccess Log level used when virtual pathway provisioning succeeds. Defaults to 'info'. * @property provisionFailure Log level used when virtual pathway provisioning fails. Defaults to 'error'. */ export type LogLevelConfig = { writeSuccess?: LogLevel; pulseSuccess?: LogLevel; pulseFailure?: LogLevel; provisionSuccess?: LogLevel; provisionFailure?: LogLevel; }; export type PathwayRuntimeEnv = "development" | "production" | "test"; export type PathwayMode = "virtual" | "managed"; export interface ManagedPathwayConfig { endpointUrl: string; authHeaders?: Record; sizeClass?: "small" | "medium" | "high"; } export interface PathwaysBuilderConfig { baseUrl: string; tenant: string; dataCore: string; apiKey: string; pathwayTimeoutMs?: number; logger?: Logger; enableSessionUserResolvers?: boolean; overrideSessionUserResolvers?: SessionUserResolver; logLevel?: LogLevelConfig; dataCoreDescription?: string; dataCoreAccessControl?: string; dataCoreDeleteProtection?: boolean; pathwayName?: string; pathwayLabels?: Record; /** @deprecated No longer used — virtual pathway commands are now poll-based */ advertisedUrl?: string; /** @deprecated No longer used — virtual pathway commands are now poll-based */ resetSecret?: string; /** @deprecated No longer used — virtual pathway commands are now poll-based */ resetPath?: string; pulseUrl?: string; pulseIntervalMs?: number; commandPollingIntervalMs?: number; runtimeEnv?: PathwayRuntimeEnv; pathwayMode?: PathwayMode; /** * Escape hatch for local control-plane work. When `runtimeEnv` is `"development"` the * by-name pathway upsert is skipped even with `autoProvision.pathway: true`, because a * pathway instance is a shared control-plane resource that every developer boot would * create, pulse, and receive restart commands for. Set this to `true` to register anyway. * * Registering from development logs a warning on every boot. Default: `false`. */ allowDevelopmentPathwayRegistration?: boolean; /** * Granular auto-provisioning toggles. Omitted fields fall back to resources-on, * pathway-off defaults — see `AutoProvisionConfig`. */ autoProvision?: AutoProvisionConfig; /** * @deprecated Prefer `autoProvision`. Mapping: * - `true` → `{ dataCore: true, flowType: true, eventType: true, pathway: false }` * - `false` → `{ dataCore: false, flowType: false, eventType: false, pathway: false }` */ defaultAutoProvision?: boolean; /** * Controls whether provisioning failures throw or are only logged. * * Defaults: * - check failures: log and continue, treated as possible Flowcore outages * - apply failures: throw, so real provisioning failures fail loud * * Passing `"throw"` or `"continue"` applies that mode to both categories. */ provisionFailure?: ProvisionFailureMode | ProvisionFailureConfig; /** Maximum number of sibling resource operations run at once. Default: 4. */ provisionConcurrency?: number; /** Retry policy for transient provisioning requests. */ provisionRetry?: ProvisionRetryConfig; managedConfig?: ManagedPathwayConfig; /** * Optional symmetric encryption for pathways marked with `encrypted`. * * When omitted, `mode: "none"`, or `mode: "symmetric"` without a key, encrypted * pathway payloads are left unchanged. When a symmetric key is supplied, the * full payload is AES-256-GCM encrypted on write and decrypted before * process-time schema validation and handlers. */ encryption?: PathwayEncryptionConfig; /** * Byte budgets for automatic splitting of oversized events. * * Flowcore rejects an event whose payload exceeds 64 000 bytes. Chunking is * off until a chunk store is configured with `withPathwayChunkStore()`. With * a store, `write()` splits such a payload into part events and `process()` * reassembles them through the store before validation, cluster routing and * handlers. Set `enabled: false` to keep chunking off even with a store. */ chunking?: PathwayChunkingConfig; } /** * SessionUserResolver is a key-value store for storing and retrieving UserIdResolver functions * with a TTL (time to live). * * This allows for session-specific user resolvers to be stored and reused across different * sessions or operations. */ export interface SessionUserResolver { /** * Retrieves a UserIdResolver from the session user resolver store * @param key The key to retrieve the UserIdResolver for * @returns The UserIdResolver or undefined if it doesn't exist */ get(key: string): Promise | UserIdResolver | undefined; /** * Stores a UserIdResolver in the session user resolver store * @param key The key to store the UserIdResolver under * @param value The UserIdResolver to store * @param ttlMs The time to live for the UserIdResolver in milliseconds */ set(key: string, value: UserIdResolver, ttlMs: number): Promise | void; } /** * SessionUserResolver implementation that uses a Map to store UserIdResolver functions * with a TTL (time to live). */ export declare class SessionUser implements SessionUserResolver { /** * The underlying Map that stores UserIdResolver functions and their timeouts * Using unknown for timeout to support both Node.js and Deno timer types */ private store; /** * Creates a new SessionUser instance */ constructor(); /** * Retrieves a UserIdResolver from the session user resolver store * @param key The key to retrieve the UserIdResolver for * @returns The UserIdResolver or undefined if it doesn't exist */ get(key: string): UserIdResolver | undefined; /** * Stores a UserIdResolver in the session user resolver store * @param key The key to store the UserIdResolver under * @param value The UserIdResolver to store * @param ttlMs The time to live for the UserIdResolver in milliseconds * @default 5 minutes */ set(key: string, value: UserIdResolver, ttlMs?: number): void; } /** * Main builder class for creating and managing Flowcore pathways * * The PathwaysBuilder provides an interface for: * - Registering pathways with type-safe schemas * - Handling events sent to pathways * - Writing data to pathways * - Managing event processing and retries * - Observing event lifecycle (before/after/error) * - Audit logging of pathway operations * * @template TPathway Record type that maps pathway keys to their payload types * @template TWritablePaths Union type of pathway keys that can be written to */ export declare class PathwaysBuilder = {}, TWritablePaths extends keyof TPathway = never> { private readonly pathways; private readonly handlers; private readonly beforeObservable; private readonly afterObservers; private readonly errorObservers; private readonly globalErrorSubject; private readonly writers; private readonly batchWriters; private readonly fileWriters; private readonly schemas; private readonly inputSchemas; private readonly writable; private readonly subscribed; private readonly pumpGroups; private readonly encryptedPathways; private readonly timeouts; private readonly maxRetries; private readonly retryDelays; private readonly filePathways; private readonly webhookBuilderFactory; private pathwayState; private chunkStore; private deliveryStore; private readonly chunking; private pathwayTimeoutMs; private auditHandler?; private userIdResolver?; private readonly sessionUserResolvers; private readonly logger; private readonly encryptionProvider; private readonly baseUrl; private readonly tenant; private readonly dataCore; private readonly apiKey; private readonly logLevel; private readonly flowTypeDescriptions; private readonly eventTypeDescriptions; private readonly dataCoreDescription?; private readonly dataCoreAccessControl; private readonly dataCoreDeleteProtection; private readonly provisionFailure?; private readonly provisionConcurrency; private readonly provisionRetry; private readonly pathwayName?; private readonly pathwayLabels; private readonly pulseUrl; private readonly pulseIntervalMs; private readonly commandPollingIntervalMs; private readonly runtimeEnv; private readonly pathwayMode; private readonly allowDevelopmentPathwayRegistration; private readonly autoProvision; private readonly managedConfig?; private pathwayId?; private clusterManager; private pathwayPump; private commandPoller; private clusterBypassProcess; private currentPumpProvisionsPathway; private currentPumpUsesExplicitPulse; private currentPumpUsesAutoPulse; /** * Creates a new PathwaysBuilder instance * @param options Configuration options for the PathwaysBuilder * @param options.baseUrl The base URL for the Flowcore API * @param options.tenant The tenant name * @param options.dataCore The data core name * @param options.apiKey The API key for authentication * @param options.pathwayTimeoutMs Optional timeout for pathway processing in milliseconds * @param options.logger Optional logger instance * @param options.enableSessionUserResolvers Whether to enable session user resolvers * @param options.overrideSessionUserResolvers Optional SessionUserResolver instance to override the default * @param options.logLevel Optional configuration for log levels * @param options.logLevel.writeSuccess Log level for write success messages ('info' or 'debug'). Defaults to 'info'. */ constructor({ baseUrl, tenant, dataCore, apiKey, pathwayTimeoutMs, logger, enableSessionUserResolvers, overrideSessionUserResolvers, logLevel, dataCoreDescription, dataCoreAccessControl, dataCoreDeleteProtection, pathwayName, pathwayLabels, advertisedUrl: _advertisedUrl, resetSecret: _resetSecret, resetPath: _resetPath, pulseUrl, pulseIntervalMs, commandPollingIntervalMs, runtimeEnv, pathwayMode, allowDevelopmentPathwayRegistration, autoProvision, defaultAutoProvision, provisionFailure, provisionConcurrency, provisionRetry, managedConfig, encryption, chunking, }: PathwaysBuilderConfig); /** * Configures the PathwaysBuilder to use a custom pathway state implementation * @param state The PathwayState implementation to use * @returns The PathwaysBuilder instance with custom state configured */ withPathwayState(state: PathwayState): PathwaysBuilder; /** * Enables automatic chunking of oversized events by configuring the store that collects * parts until they can be reassembled. * * Without a store, chunking is off: an oversized write fails with the Flowcore size error, * and a received part event throws. Use `createPostgresPathwayChunkStore()` whenever more * than one instance consumes the same pathways. `InternalPathwayChunkStore` is in-memory and * only suitable for a single process. * @param store The PathwayChunkStore implementation to use * @returns The PathwaysBuilder instance with the chunk store configured */ /** * Configure where a delivery pause is stored. * * A pause issued by the control plane must outlive the process. The default store is * IN MEMORY, so a redeploy or a leader change brings the pathway back delivering with * no operator action. Pass a durable implementation before relying on a pause in * production — for example {@link PostgresPathwayDeliveryStore}. */ withPathwayDeliveryStore(store: PathwayDeliveryStore): PathwaysBuilder; withPathwayChunkStore(store: PathwayChunkStore): PathwaysBuilder; /** * Configures the PathwaysBuilder to use audit functionality * @param handler The handler function that receives pathway and event information * @returns The PathwaysBuilder instance with audit configured */ withAudit(handler: AuditHandler): PathwaysBuilder; /** * Configures the PathwaysBuilder to use a custom user ID resolver * @param resolver The resolver function that resolves to the current user ID * @returns The PathwaysBuilder instance with custom user ID resolver configured */ withUserResolver(resolver: UserIdResolver): PathwaysBuilder; /** * Registers a user resolver for a specific session * * Session-specific user resolvers allow you to associate different user IDs with different * sessions, which is useful in multi-user applications or when tracking user actions across * different sessions. * * The resolver is stored in a key-value store with a TTL (time to live), and will be used * to resolve the user ID when operations are performed with the given session ID. If the resolver * expires, it will need to be registered again. * * This feature works in conjunction with the SessionPathwayBuilder to provide a complete * session management solution. * * @param sessionId The session ID to associate with this resolver * @param resolver The resolver function that resolves to the user ID for this session * @returns The PathwaysBuilder instance for chaining * * @throws Error if session user resolvers are not configured (sessionUserResolvers not provided in constructor) * * @example * ```typescript * // Register a resolver for a specific session * pathwaysBuilder.withSessionUserResolver("session-123", async () => { * return "user-456"; // Return the user ID for this session * }); * * // Use with SessionPathwayBuilder * const session = new SessionPathwayBuilder(pathwaysBuilder, "session-123"); * await session.write("user/action", actionData); * // The user ID will be automatically included in the metadata * ``` */ withSessionUserResolver(sessionId: string, resolver: UserIdResolver): PathwaysBuilder; /** * Gets a user resolver for a specific session ID * @param sessionId The session ID to get the resolver for * @returns The resolver function for the session, or undefined if none exists */ getSessionUserResolver(sessionId: string): UserIdResolver | undefined; /** * Process a pathway event with error handling and retries * @param pathway The pathway to process * @param data The event data to process * @returns Promise that resolves when processing is complete */ process(pathway: keyof TPathway, data: FlowcoreEvent): Promise; /** * Records one part of an oversized event. * * @returns `null` when the chunk is still incomplete (the part is stored and, unless it is * part 1, marked processed). When this part completes the chunk, `data` is mutated into the * reassembled logical event and the chunk id plus every part event id are returned. */ private collectChunkPart; /** * Marks the remaining part events processed and removes the chunk from the store. * Part 1 is handled by the regular processing path (it is the logical event id). */ private finishChunk; private decryptChunkSlice; /** * Runs the regular processing path for an event whose payload is in its final wire form * (decryption still pending, reassembly done). */ private processResolvedEvent; /** * Registers a new pathway with the given contract * @template F The flow type string * @template E The event type string * @template S The schema type extending ZodTypeAny * @template W Boolean indicating if the pathway is writable (defaults to true) * @param contract The pathway contract describing the pathway * @returns The PathwaysBuilder instance with the new pathway registered */ register(contract: PathwayContract & { writable?: W; /** * Whether the in-process pump should subscribe to and consume events for * this pathway. Defaults to `true`. Set to `false` for analytical / * write-only pathways: `.write()` still works, but the pump does not pull * events from flowcore and `.handle()` will never fire. Useful when * events are written for history/analytics but no in-service consumer * exists, avoiding back-pressure on the shared pump queue. */ subscribe?: boolean; /** * Optional pump group. Event types sharing a `(flowType, pumpGroup)` pair * land on the same data pump; different `pumpGroup` values within one * `flowType` run on independent pumps with isolated state cursors and * processor concurrency. Omit (or pass `"default"`) for the legacy * single-pump-per-flowType behavior. * * NOTE: the WebSocket notifier is `flowType`-scoped, so two groups on the * same `flowType` receive identical notifications and each pulls; isolation * is at processor + state, not bandwidth. */ pumpGroup?: string; maxRetries?: number; retryDelayMs?: number; isFilePathway?: FP; encrypted?: boolean; }): PathwaysBuilder, { output: FP extends true ? z.infer & z.infer : z.infer; input: FP extends true ? z.input & z.input : z.input; }>, TWritablePaths | WritablePathway, W>>; /** * Gets a pathway instance by its path * * @template TPath The specific pathway key to retrieve * @param path The pathway key to get * @returns The pathway instance */ get(path: TPath): TPathway[TPath]["output"]; /** * Sets a handler function for a pathway * * This handler will be called whenever an event is received for the specified pathway. * Only one handler can be registered per pathway in a given PathwaysBuilder instance. * * @template TPath The specific pathway key to handle * @param path The pathway key to handle * @param handler The function that will process events for this pathway * @throws Error if the pathway doesn't exist or already has a handler */ handle(path: TPath, handler: (event: FlowcoreEvent) => Promise | void): PathwaysBuilder; /** * Subscribe to pathway events (before or after processing) * @param path The pathway to subscribe to * @param handler The handler function for the events * @param type The event type to subscribe to (before, after, or all) */ subscribe(path: TPath, handler: (event: FlowcoreEvent) => void, type?: "before" | "after" | "all"): PathwaysBuilder; /** * Subscribe to errors for a specific pathway * @param path The pathway to subscribe to errors for * @param handler The handler function that receives the error and event */ onError(path: TPath, handler: (error: Error, event: FlowcoreEvent) => void): PathwaysBuilder; /** * Subscribe to errors for all pathways * @param handler The handler function that receives the error, event, and pathway name */ onAnyError(handler: (error: Error, event: FlowcoreEvent, pathway: string) => void): PathwaysBuilder; /** * Writes data to a pathway with optional audit metadata * @param path The pathway to write to * @param data The data to write * @param metadata Optional metadata to include with the event * @param options Optional write options * @returns A promise that resolves to the event ID(s) */ write(path: TPath, input: { batch?: B; data: B extends true ? TPathway[TPath]["input"][] : TPathway[TPath]["input"]; metadata?: EventMetadata; options?: PathwayWriteOptions; }): Promise; /** * Decides whether any item of a write exceeds the event size cap and, if so, expands it into * part envelopes. * * @param path The pathway being written * @param plaintext The validated plaintext payload (array when `batch`) * @param wire The payload as it would be sent (encrypted envelope when applicable) * @param batch Whether this is a batch write * @param encrypted Whether `wire` is encrypted * @returns `null` when nothing needs splitting, otherwise the wire items to send and a mapper * from the returned raw event ids to one logical id per input item (the id of part 1) */ private planChunkedWrite; private encryptPathwayPayload; /** * Whether an incoming event still carries an encrypted payload that this pathway must decrypt. * Pathways that are not registered with `encrypted: true` are left alone even when the marker is * present — those carry an application-level envelope the handler owns. */ private shouldDecryptPathwayPayload; private decryptPathwayPayload; private hasEncryptedPayloadMetadata; /** * Waits for a specific event to be processed * * This method polls the pathway state to check if an event has been processed, * with a configurable timeout. It will throw an error if the timeout is exceeded. * * @private * @param eventId The ID of the event to wait for * @returns Promise that resolves when the event is processed * @throws Error if the timeout is exceeded */ private waitForPathwayToBeProcessed; /** * Start cluster mode for distributed event processing. * This instance joins the cluster and either becomes a leader (distributes events) * or a worker (receives and processes events). */ startCluster(options: PathwayClusterOptions): Promise; /** * Stop cluster mode */ stopCluster(): Promise; /** * The local data pump, when one is running. * * Use it to pause, resume or inspect delivery in code. Returns `null` before * `startPump()` and on a cluster follower, which runs no pump. */ get pump(): PathwayPump | null; /** * Whether cluster mode is currently active */ get isClusterActive(): boolean; private buildAutoPulseConfig; private applyAutoPulseConfig; private startCommandPollerIfNeeded; /** * Execute one control-plane command against the local pump. * * Every command shares one target filter, built from `sourceFlowTypes`. Entries are * either a bare flow type (`"orders.0"`, every pump group on it) or a composite * `"orders.0::hot"` naming exactly one pump. An empty or missing list targets every pump. * * THROWS on an unknown command type, and on a filter that matches no pump. Both cases * mean the command did nothing, and the poller must report `failed` rather than let the * control plane record a success that never happened. */ private executePathwayCommand; /** * A command whose filter matched no pump did nothing. Reporting success there lets an * operator believe a pathway is paused when it is still delivering, so fail loudly. */ private assertCommandMatched; /** * Key under which this deployable's delivery state is stored. Store implementations own * their own table naming and prefixing, exactly as the pump state store does. */ private get deliveryStateKey(); /** Write the current set of paused pumps so it survives a restart. */ private persistDeliveryState; /** * Re-apply a persisted pause before the pump starts delivering. * * Runs on every leader bootstrap, so a redeploy, a pod restart and a cluster leader * change all come back paused at the same position rather than quietly resuming. */ /** * Read the desired delivery state from the control plane. * * The CP is the source of truth: an operator pauses there, and this instance may never * have seen the command. Returns null when the CP cannot answer, so the caller can fall * back to the local cache rather than resume a paused pathway during a network blip. */ private fetchDeliveryStateFromControlPlane; /** Every `${flowType}::${pumpGroup}` key this builder will run. */ private buildAllPumpKeys; /** * Turn control-plane targets into concrete pump keys. A bare flow type expands to every * pump group registered on it; a composite key is already exact. */ private expandDeliveryTargets; private restoreDeliveryState; /** Drop stored pause keys that match no current registration, after the pumps started. */ private pruneDeliveryState; private stopCommandPoller; private startCurrentPump; /** * Returns the subset of registrations the in-process pump should subscribe to, * enriched with the resolved `pumpGroup` for each pathway. Only used by the * pump — provisioning still uses {@link buildRegistrations} (no pumpGroup). */ private buildPumpRegistrations; private stopLeaderRuntime; private bootstrapLeaderPump; private handleLeadershipChange; private buildRegistrations; /** * Returns the subset of registrations the in-process pump should subscribe to. * Pathways registered with `subscribe: false` are excluded — they are * write-only / analytical-only and the pump must not pull their events. * Provisioning still uses {@link buildRegistrations} so the flow type and * event type are created in flowcore for every registered pathway. */ private buildSubscribedRegistrations; private provisionSharedResources; private getPathwayProvisionAuthHeader; private ensurePathwayName; private shouldContinueProvisionApplyFailure; private buildManagedPathwayConfig; private upsertPathwayByName; /** * A pathway instance is a shared control-plane resource. Development boots must never create * one — every developer laptop would otherwise leave a stray pathway, pulse it, and poll for * restart commands aimed at a real deployment. Opt in explicitly with * `allowDevelopmentPathwayRegistration` when doing local control-plane work. */ private canRegisterPathwayInstance; private registerPathwayInstance; /** * Provision Flowcore infrastructure honoring `autoProvision`. * * By default this only provisions shared resources (data core, flow types, event types) * because `DEFAULT_AUTO_PROVISION.pathway` is `false`. To also upsert the pathway * instance (managed or virtual by-name), opt in via builder-level * `autoProvision: { pathway: true }` or pass an override here. * * `runtimeEnv: "development"` never upserts a pathway instance, whatever `autoProvision.pathway` * says — see `allowDevelopmentPathwayRegistration` for the explicit escape hatch. * * Additive only — never deletes. */ provision(autoProvisionOverride?: boolean | AutoProvisionConfig): Promise; /** * Start the data pump to auto-fetch events from Flowcore. * If cluster is active, pump only runs on the leader instance. */ startPump(options: PathwayPumpOptions): Promise; /** * Stop the data pump */ stopPump(): Promise; /** * Reset the data pump to a specific position or clear state and restart. * In cluster mode, the request is routed to the leader automatically. * * @param position - Target position { timeBucket, eventId? }. If omitted, clears persisted state * and restarts from the live position. To replay from the very beginning, * pass the first time bucket explicitly. * @param filter - Optional filter narrowing which pumps to reset. Accepts: * - `string[]` (legacy): treated as `flowTypes`. * - `{ flowTypes?, pumpGroups? }`: matches pumps that satisfy every supplied * criterion (intersection). * Cluster mode does not yet propagate the filter — it is logged and * the leader resets all pumps. */ resetPump(position?: PumpState, filter?: string[] | { flowTypes?: string[]; pumpGroups?: string[]; }): Promise; /** * Converts a Zod validation error to a human-readable string * @param error The Zod validation error to convert * @returns A formatted error message string */ private validationErrorToString; } //# sourceMappingURL=builder.d.ts.map