@multiplier-labs/stepflow 0.3.4 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/{chunk-5XJBAPXF.js → chunk-4IDPWWGS.js} +15 -4
- package/dist/chunk-4IDPWWGS.js.map +1 -0
- package/dist/{chunk-U3PCVK6O.js → chunk-T6GAZBTQ.js} +70 -38
- package/dist/chunk-T6GAZBTQ.js.map +1 -0
- package/dist/{chunk-FMPJBK2N.js → chunk-TVSADUYU.js} +3 -5
- package/dist/{chunk-FMPJBK2N.js.map → chunk-TVSADUYU.js.map} +1 -1
- package/dist/events/index.d.ts +43 -3
- package/dist/events/index.js +7 -3
- package/dist/{index-Cd0IZ40L.d.ts → index-CdXDv30T.d.ts} +10 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +159 -46
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/storage/index.d.ts +6 -4
- package/dist/storage/index.js +1 -1
- package/dist/{types-DSEjiLMj.d.ts → types-BfWe5yP6.d.ts} +2 -0
- package/package.json +26 -14
- package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
- package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
- package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
- package/.github/CODEOWNERS +0 -15
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/publish.yml +0 -42
- package/.github/workflows/security.yml +0 -29
- package/CHANGELOG.md +0 -34
- package/SECURITY.md +0 -40
- package/dist/chunk-5XJBAPXF.js.map +0 -1
- package/dist/chunk-U3PCVK6O.js.map +0 -1
- package/docs/github-packages-setup.md +0 -97
- package/docs/stepflow-api-reference.md +0 -1663
- package/src/core/engine/concurrency.ts +0 -229
- package/src/core/engine/lifecycle.ts +0 -176
- package/src/core/engine.test.ts +0 -1435
- package/src/core/engine.ts +0 -598
- package/src/core/index.ts +0 -7
- package/src/core/orchestrator/persistence.ts +0 -165
- package/src/core/orchestrator/plan-resolution.ts +0 -140
- package/src/core/orchestrator/state-transitions.ts +0 -109
- package/src/core/orchestrator/step-runner.ts +0 -427
- package/src/core/orchestrator.ts +0 -252
- package/src/core/types.ts +0 -220
- package/src/events/index.ts +0 -8
- package/src/events/memory.test.ts +0 -107
- package/src/events/memory.ts +0 -92
- package/src/events/socketio.test.ts +0 -378
- package/src/events/socketio.ts +0 -262
- package/src/events/types.ts +0 -118
- package/src/events/webhook.test.ts +0 -949
- package/src/events/webhook.ts +0 -807
- package/src/index.ts +0 -270
- package/src/planning/evaluate-condition.test.ts +0 -153
- package/src/planning/evaluate-condition.ts +0 -105
- package/src/planning/index.ts +0 -55
- package/src/planning/planner.ts +0 -494
- package/src/planning/planning.test.ts +0 -727
- package/src/planning/registry.test.ts +0 -398
- package/src/planning/registry.ts +0 -283
- package/src/planning/types.ts +0 -566
- package/src/scheduler/cron.test.ts +0 -479
- package/src/scheduler/cron.ts +0 -438
- package/src/scheduler/index.ts +0 -8
- package/src/scheduler/postgres-persistence.ts +0 -530
- package/src/scheduler/sqlite-persistence.test.ts +0 -235
- package/src/scheduler/sqlite-persistence.ts +0 -286
- package/src/scheduler/types.ts +0 -73
- package/src/storage/index.ts +0 -27
- package/src/storage/memory.test.ts +0 -349
- package/src/storage/memory.ts +0 -227
- package/src/storage/postgres-core.ts +0 -550
- package/src/storage/postgres-migration.test.ts +0 -69
- package/src/storage/postgres-transaction-pagination.test.ts +0 -107
- package/src/storage/postgres.ts +0 -835
- package/src/storage/sqlite-core.ts +0 -151
- package/src/storage/sqlite.test.ts +0 -642
- package/src/storage/sqlite.ts +0 -717
- package/src/storage/types.ts +0 -343
- package/src/utils/errors.test.ts +0 -150
- package/src/utils/errors.ts +0 -161
- package/src/utils/id.ts +0 -21
- package/src/utils/index.ts +0 -10
- package/src/utils/logger.test.ts +0 -121
- package/src/utils/logger.ts +0 -96
- package/src/utils/pinned-fetch.test.ts +0 -84
- package/src/utils/pinned-fetch.ts +0 -118
- package/src/utils/postgres-deps.ts +0 -79
- package/src/utils/retry.test.ts +0 -177
- package/src/utils/retry.ts +0 -121
- package/src/utils/safe-json.test.ts +0 -166
- package/src/utils/safe-json.ts +0 -107
- package/src/utils/webhook-signature.test.ts +0 -111
- package/src/utils/webhook-signature.ts +0 -74
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -14
- package/vitest.config.ts +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events/memory.ts","../src/events/socketio.ts","../src/events/webhook.ts","../src/utils/pinned-fetch.ts","../src/utils/webhook-signature.ts"],"sourcesContent":["/**\n * In-memory event transport using Node.js EventEmitter.\n * Default transport for development and single-process deployments.\n *\n * NOTE: This adapter is intended for development and testing only. It stores\n * all state in-process with no persistence or multi-process safety guarantees.\n */\n\nimport { EventEmitter } from 'events';\nimport type {\n EventTransport,\n WorkflowEvent,\n WorkflowEventType,\n EventCallback,\n Unsubscribe,\n} from './types';\n\n/**\n * In-memory event transport implementation using EventEmitter.\n * Events are emitted to subscribers in the same process.\n */\nexport class MemoryEventTransport implements EventTransport {\n private emitter = new EventEmitter();\n\n constructor() {\n // Increase max listeners to avoid warnings in workflows with many steps\n this.emitter.setMaxListeners(100);\n }\n\n /**\n * Emit an event to all subscribers.\n * Each channel is isolated so a failing listener on one channel\n * cannot prevent delivery to the others.\n */\n emit(event: WorkflowEvent): void {\n // Emit to run-specific subscribers first (most targeted)\n this.emitter.emit(`run:${event.runId}`, event);\n\n // Emit to event-type subscribers\n this.emitter.emit(`type:${event.eventType}`, event);\n\n // Emit to global subscribers last (broadest, most likely to have side effects)\n this.emitter.emit('event', event);\n }\n\n /**\n * Subscribe to events for a specific run.\n */\n subscribe(runId: string, callback: EventCallback): Unsubscribe {\n const channel = `run:${runId}`;\n this.emitter.on(channel, callback);\n return () => {\n this.emitter.off(channel, callback);\n };\n }\n\n /**\n * Subscribe to all events.\n */\n subscribeAll(callback: EventCallback): Unsubscribe {\n this.emitter.on('event', callback);\n return () => {\n this.emitter.off('event', callback);\n };\n }\n\n /**\n * Subscribe to events of a specific type.\n */\n subscribeToType(eventType: WorkflowEventType, callback: EventCallback): Unsubscribe {\n const channel = `type:${eventType}`;\n this.emitter.on(channel, callback);\n return () => {\n this.emitter.off(channel, callback);\n };\n }\n\n /**\n * Close the transport and remove all listeners.\n */\n close(): void {\n this.emitter.removeAllListeners();\n }\n\n /**\n * Get the number of listeners for a channel (for testing).\n */\n getListenerCount(channel?: string): number {\n if (channel) {\n return this.emitter.listenerCount(channel);\n }\n return this.emitter.listenerCount('event');\n }\n}\n","/**\n * Socket.IO Event Transport\n *\n * Broadcasts workflow events to connected clients via Socket.IO.\n * Clients can subscribe to specific run IDs or receive all events.\n */\n\nimport type { EventTransport, EventCallback, Unsubscribe, WorkflowEvent } from './types';\nimport type { Logger } from '../core/types';\n\n/**\n * Minimal Socket.IO Server interface.\n * This allows using the adapter without a direct dependency on socket.io.\n */\nexport interface SocketIOServer {\n to(room: string): {\n emit(event: string, ...args: unknown[]): void;\n };\n emit(event: string, ...args: unknown[]): void;\n}\n\n/**\n * Minimal Socket.IO Socket interface.\n */\nexport interface SocketIOSocket {\n join(room: string): void;\n leave(room: string): void;\n on(event: string, callback: (...args: unknown[]) => void): void;\n}\n\n/**\n * Authorization callback for Socket.IO run subscriptions.\n * Return true to allow the subscription, false to deny it.\n */\nexport type SocketIOAuthorizeFn = (\n runId: string,\n socket: SocketIOSocket\n) => boolean | Promise<boolean>;\n\n/**\n * Configuration for SocketIOEventTransport.\n */\nexport interface SocketIOEventTransportConfig {\n /** Socket.IO server instance */\n io: SocketIOServer;\n\n /** Event name to use for workflow events (default: 'workflow:event') */\n eventName?: string;\n\n /** Room prefix for run-specific subscriptions (default: 'run:') */\n roomPrefix?: string;\n\n /** Whether to also broadcast to a global room (default: true) */\n broadcastGlobal?: boolean;\n\n /** Global room name (default: 'workflow:all') */\n globalRoom?: string;\n\n /** Logger for transport errors (default: console-based) */\n logger?: Logger;\n}\n\n/**\n * Socket.IO-based event transport for real-time workflow events.\n *\n * @example\n * ```typescript\n * import { Server } from 'socket.io';\n * import { SocketIOEventTransport } from 'stepflow';\n *\n * const io = new Server(httpServer);\n * const eventTransport = new SocketIOEventTransport({ io });\n *\n * const engine = new WorkflowEngine({\n * storage,\n * events: eventTransport,\n * });\n *\n * // Client-side:\n * // socket.emit('workflow:subscribe', runId);\n * // socket.on('workflow:event', (event) => console.log(event));\n * ```\n */\nexport class SocketIOEventTransport implements EventTransport {\n private io: SocketIOServer;\n private eventName: string;\n private roomPrefix: string;\n private broadcastGlobal: boolean;\n private globalRoom: string;\n private logger: Logger;\n\n // Local subscribers for server-side subscriptions\n private runSubscribers = new Map<string, Set<EventCallback>>();\n private globalSubscribers = new Set<EventCallback>();\n\n constructor(config: SocketIOEventTransportConfig) {\n this.io = config.io;\n this.eventName = config.eventName ?? 'workflow:event';\n this.roomPrefix = config.roomPrefix ?? 'run:';\n this.broadcastGlobal = config.broadcastGlobal ?? true;\n this.globalRoom = config.globalRoom ?? 'workflow:all';\n this.logger = config.logger ?? {\n debug() {},\n info() {},\n warn() {},\n error(message: string, ...args: unknown[]) { console.error(message, ...args); },\n };\n }\n\n /**\n * Emit an event to Socket.IO clients and local subscribers.\n */\n emit(event: WorkflowEvent): void {\n // Serialize the event (convert Date to ISO string for transport)\n const serializedEvent = {\n ...event,\n timestamp: event.timestamp.toISOString(),\n };\n\n // Emit to run-specific room\n const room = `${this.roomPrefix}${event.runId}`;\n this.io.to(room).emit(this.eventName, serializedEvent);\n\n // Emit to global room\n if (this.broadcastGlobal) {\n this.io.to(this.globalRoom).emit(this.eventName, serializedEvent);\n }\n\n // Notify local subscribers\n const runSubs = this.runSubscribers.get(event.runId);\n if (runSubs) {\n for (const callback of runSubs) {\n try {\n callback(event);\n } catch (error) {\n this.logger.error('Event callback error:', error);\n }\n }\n }\n\n for (const callback of this.globalSubscribers) {\n try {\n callback(event);\n } catch (error) {\n this.logger.error('Event callback error:', error);\n }\n }\n }\n\n /**\n * Subscribe to events for a specific run (server-side).\n */\n subscribe(runId: string, callback: EventCallback): Unsubscribe {\n let subs = this.runSubscribers.get(runId);\n if (!subs) {\n subs = new Set();\n this.runSubscribers.set(runId, subs);\n }\n subs.add(callback);\n\n return () => {\n subs!.delete(callback);\n if (subs!.size === 0) {\n this.runSubscribers.delete(runId);\n }\n };\n }\n\n /**\n * Subscribe to all events (server-side).\n */\n subscribeAll(callback: EventCallback): Unsubscribe {\n this.globalSubscribers.add(callback);\n return () => {\n this.globalSubscribers.delete(callback);\n };\n }\n\n /**\n * Set up client subscription handlers on a socket.\n * Call this when a client connects to enable subscription commands.\n *\n * An `authorize` callback is required to prevent unauthorized access to run events.\n * The callback receives the run ID (or `'*'` for global subscriptions) and the socket,\n * and must return `true` to allow or `false` to deny the subscription.\n *\n * @param socket - The Socket.IO socket to set up handlers on\n * @param authorize - Callback to check if a socket can access a run.\n *\n * @example\n * ```typescript\n * io.on('connection', (socket) => {\n * eventTransport.setupClientHandlers(socket, async (runId, sock) => {\n * // Check if the authenticated user owns this run\n * const userId = sock.data?.userId;\n * return userId ? await canUserAccessRun(userId, runId) : false;\n * });\n * });\n * ```\n */\n setupClientHandlers(\n socket: SocketIOSocket,\n authorize: SocketIOAuthorizeFn\n ): void {\n // Subscribe to a specific run\n socket.on('workflow:subscribe', async (...args: unknown[]) => {\n const runId = args[0];\n if (typeof runId !== 'string') return;\n\n try {\n const allowed = await authorize(runId, socket);\n if (!allowed) {\n this.logger.warn(`Subscription denied for run ${runId}`);\n return;\n }\n } catch (error) {\n this.logger.error('Authorization check failed:', error);\n return;\n }\n\n socket.join(`${this.roomPrefix}${runId}`);\n });\n\n // Unsubscribe from a specific run\n socket.on('workflow:unsubscribe', (...args: unknown[]) => {\n const runId = args[0];\n if (typeof runId === 'string') {\n socket.leave(`${this.roomPrefix}${runId}`);\n }\n });\n\n // Subscribe to all events (requires authorization to pass with '*' as runId)\n socket.on('workflow:subscribe:all', async () => {\n try {\n const allowed = await authorize('*', socket);\n if (!allowed) {\n this.logger.warn('Global subscription denied');\n return;\n }\n } catch (error) {\n this.logger.error('Authorization check failed:', error);\n return;\n }\n\n socket.join(this.globalRoom);\n });\n\n // Unsubscribe from all events\n socket.on('workflow:unsubscribe:all', () => {\n socket.leave(this.globalRoom);\n });\n }\n\n /**\n * Clear all in-process server-side subscribers.\n * The underlying Socket.IO server socket is not closed here — manage its lifecycle externally.\n */\n close(): void {\n this.runSubscribers.clear();\n this.globalSubscribers.clear();\n }\n}\n","/**\n * Webhook Event Transport\n *\n * Posts workflow events to HTTP endpoints via webhooks.\n * Supports multiple webhooks with optional filtering by event type.\n */\n\nimport { promises as dns } from 'node:dns';\nimport { isIPv4, isIPv6 } from 'node:net';\nimport { pinnedFetch } from '../utils/pinned-fetch';\nimport type { EventTransport, EventCallback, Unsubscribe, WorkflowEvent, WorkflowEventType } from './types';\nimport type { Logger } from '../core/types';\n\n/**\n * Webhook endpoint configuration.\n */\nexport interface WebhookEndpoint {\n /** Unique identifier for this endpoint */\n id: string;\n\n /** URL to POST events to */\n url: string;\n\n /** Optional secret for signing payloads (HMAC-SHA256) */\n secret?: string;\n\n /** Optional filter for specific event types (empty = all events) */\n eventTypes?: WorkflowEventType[];\n\n /** Optional filter for specific workflow kinds (empty = all kinds) */\n workflowKinds?: string[];\n\n /** Optional custom headers to include */\n headers?: Record<string, string>;\n\n /** Whether this endpoint is enabled (default: true) */\n enabled?: boolean;\n\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number;\n\n /** Number of retry attempts on failure (default: 3) */\n retries?: number;\n}\n\n/**\n * Configuration for WebhookEventTransport.\n */\nexport interface WebhookEventTransportConfig {\n /** Initial webhook endpoints */\n endpoints?: WebhookEndpoint[];\n\n /** Default timeout for all webhooks in ms (default: 5000) */\n defaultTimeout?: number;\n\n /** Default retry attempts (default: 3) */\n defaultRetries?: number;\n\n /** Retry delay in ms (default: 1000) */\n retryDelay?: number;\n\n /** Custom fetch function (for testing or custom HTTP clients) */\n fetchFn?: typeof fetch;\n\n /**\n * Whether to allow non-HTTPS URLs (default: false).\n * Set to true only in development environments.\n */\n allowInsecureUrls?: boolean;\n\n /**\n * Maximum payload size in bytes (default: 1048576 = 1 MB).\n * Payloads exceeding this limit will be rejected.\n */\n maxPayloadBytes?: number;\n\n /**\n * Maximum concurrent webhook requests across all endpoints (default: 50).\n * Requests beyond this limit are queued.\n */\n maxConcurrentRequests?: number;\n\n /**\n * Maximum number of queued webhook deliveries waiting for a concurrency slot\n * (default: 1000, env override: `STEPFLOW_WEBHOOK_MAX_QUEUE_DEPTH`).\n *\n * When the queue is full, incoming `emit()` deliveries are dropped to\n * protect the process from memory exhaustion under burst traffic. Drops\n * are logged via `logger.warn` with the endpoint id, current queue depth,\n * and a recommended `retryAfterMs`. A value of `0` disables the cap (not\n * recommended in production).\n */\n maxQueueDepth?: number;\n\n /**\n * Suggested back-off in milliseconds reported on dropped deliveries\n * (default: 1000). Surfaced in the warning log so external orchestrators\n * (sidecars, sweeper jobs) can respect it like an HTTP `Retry-After`.\n */\n queueDropRetryAfterMs?: number;\n\n /** Logger for webhook transport errors (default: console-based) */\n logger?: import('../core/types').Logger;\n}\n\n/**\n * Webhook payload structure sent to external endpoints.\n */\nexport interface WebhookPayload {\n /**\n * The workflow event with `timestamp` serialized as an ISO 8601 string.\n * The `Date` is converted to a string before delivery so that JSON consumers\n * receive a portable, timezone-aware representation without needing Date parsing.\n */\n event: Omit<WorkflowEvent, 'timestamp'> & { timestamp: string };\n /** ISO 8601 timestamp of when this payload was delivered. */\n deliveredAt: string;\n /** ID of the webhook endpoint that received this delivery. */\n webhookId: string;\n}\n\n/**\n * Webhook-based event transport for integrating with external systems.\n *\n * @example\n * ```typescript\n * const webhookTransport = new WebhookEventTransport({\n * endpoints: [\n * {\n * id: 'slack-notifications',\n * url: 'https://hooks.slack.com/...',\n * eventTypes: ['run.completed', 'run.failed'],\n * },\n * {\n * id: 'analytics',\n * url: 'https://api.analytics.com/events',\n * secret: process.env.WEBHOOK_SECRET, // Use a cryptographically random secret (≥ 32 bytes)\n * },\n * ],\n * });\n *\n * const engine = new WorkflowEngine({\n * storage,\n * events: webhookTransport,\n * });\n * ```\n */\nexport class WebhookEventTransport implements EventTransport {\n private endpoints = new Map<string, WebhookEndpoint>();\n private defaultTimeout: number;\n private defaultRetries: number;\n private retryDelay: number;\n private fetchFn: typeof fetch;\n /**\n * Whether `fetchFn` was caller-supplied. When `false` the default fetch is\n * replaced with `pinnedFetch` so the TCP connection can target the IP that\n * was just validated (closes the DNS-rebinding TOCTOU window). When `true`\n * the custom fetch is used as-is so test injection still works.\n */\n private hasCustomFetch: boolean;\n private allowInsecureUrls: boolean;\n private maxPayloadBytes: number;\n private maxConcurrentRequests: number;\n private maxQueueDepth: number;\n private queueDropRetryAfterMs: number;\n private activeRequests = 0;\n private requestQueue: Array<() => void> = [];\n private droppedRequests = 0;\n private logger: Logger;\n\n // DNS resolution cache with TTL — stores `family` so the pin can reuse it\n // without a second lookup.\n private dnsCache = new Map<string, { address: string; family: 4 | 6; expiresAt: number }>();\n private static readonly DNS_CACHE_TTL_MS = 30_000; // 30 seconds\n private static readonly DNS_TIMEOUT_MS = 5_000; // 5 seconds\n\n // Local subscribers for server-side subscriptions\n private runSubscribers = new Map<string, Set<EventCallback>>();\n private globalSubscribers = new Set<EventCallback>();\n\n constructor(config: WebhookEventTransportConfig = {}) {\n this.defaultTimeout = config.defaultTimeout ?? 5000;\n this.defaultRetries = config.defaultRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.hasCustomFetch = config.fetchFn !== undefined;\n this.fetchFn = config.fetchFn ?? globalThis.fetch;\n this.allowInsecureUrls = config.allowInsecureUrls ?? false;\n this.maxPayloadBytes = config.maxPayloadBytes ?? 1_048_576; // 1 MB\n this.maxConcurrentRequests = config.maxConcurrentRequests ?? 50;\n if (this.maxConcurrentRequests <= 0) {\n throw new Error('maxConcurrentRequests must be greater than 0');\n }\n this.maxQueueDepth = resolveMaxQueueDepth(config.maxQueueDepth);\n if (this.maxQueueDepth < 0) {\n throw new Error('maxQueueDepth must be >= 0 (0 disables the cap)');\n }\n this.queueDropRetryAfterMs = config.queueDropRetryAfterMs ?? 1000;\n this.logger = config.logger ?? {\n debug() {},\n info() {},\n warn() {},\n error(message: string, ...args: unknown[]) { console.error(message, ...args); },\n };\n\n if (this.allowInsecureUrls) {\n this.logger.warn('WebhookEventTransport: allowInsecureUrls is enabled. Webhook payloads will be sent over HTTP. Do not use this setting in production.');\n }\n\n // Register initial endpoints (validates URLs)\n if (config.endpoints) {\n for (const endpoint of config.endpoints) {\n this.addEndpoint(endpoint);\n }\n }\n }\n\n /**\n * Add a webhook endpoint.\n * Validates the URL to prevent SSRF attacks.\n * @throws Error if the URL scheme is not allowed\n */\n addEndpoint(endpoint: WebhookEndpoint): void {\n this.validateWebhookUrl(endpoint.url);\n this.endpoints.set(endpoint.id, { enabled: true, ...endpoint });\n }\n\n /**\n * Remove a webhook endpoint.\n */\n removeEndpoint(id: string): boolean {\n return this.endpoints.delete(id);\n }\n\n /**\n * Get all registered endpoints.\n */\n getEndpoints(): WebhookEndpoint[] {\n return Array.from(this.endpoints.values());\n }\n\n /**\n * Enable or disable an endpoint.\n */\n setEndpointEnabled(id: string, enabled: boolean): void {\n const endpoint = this.endpoints.get(id);\n if (endpoint) {\n endpoint.enabled = enabled;\n }\n }\n\n /**\n * Emit an event to all matching webhook endpoints.\n */\n emit(event: WorkflowEvent): void {\n // Notify local subscribers first (synchronously)\n const runSubs = this.runSubscribers.get(event.runId);\n if (runSubs) {\n for (const callback of runSubs) {\n try {\n callback(event);\n } catch (error) {\n this.logger.error('Event callback error:', error);\n }\n }\n }\n\n for (const callback of this.globalSubscribers) {\n try {\n callback(event);\n } catch (error) {\n this.logger.error('Event callback error:', error);\n }\n }\n\n // Send to webhooks asynchronously with concurrency control\n for (const endpoint of this.endpoints.values()) {\n if (!endpoint.enabled) continue;\n if (!this.matchesFilter(event, endpoint)) continue;\n\n const accepted = this.enqueueRequest(\n () =>\n this.sendWebhook(endpoint, event).catch((error) => {\n this.logger.error(`Webhook ${endpoint.id} failed:`, error);\n }),\n endpoint.id\n );\n\n if (!accepted) {\n // Queue overflow — equivalent to a server returning 429 with Retry-After.\n // The delivery is dropped to protect the process from memory exhaustion.\n this.droppedRequests++;\n this.logger.warn(\n `Webhook ${endpoint.id} delivery dropped: queue full ` +\n `(depth ${this.requestQueue.length}/${this.maxQueueDepth}, ` +\n `active ${this.activeRequests}/${this.maxConcurrentRequests}, ` +\n `total drops ${this.droppedRequests}, retryAfterMs ${this.queueDropRetryAfterMs})`\n );\n }\n }\n }\n\n /**\n * Number of webhook deliveries dropped due to queue overflow since startup.\n * Useful for surfacing back-pressure to operators (metrics/health checks).\n */\n getDroppedRequestCount(): number {\n return this.droppedRequests;\n }\n\n /**\n * Current depth of the pending request queue.\n */\n getQueueDepth(): number {\n return this.requestQueue.length;\n }\n\n /**\n * Subscribe to events for a specific run (server-side).\n */\n subscribe(runId: string, callback: EventCallback): Unsubscribe {\n let subs = this.runSubscribers.get(runId);\n if (!subs) {\n subs = new Set();\n this.runSubscribers.set(runId, subs);\n }\n subs.add(callback);\n\n return () => {\n subs!.delete(callback);\n if (subs!.size === 0) {\n this.runSubscribers.delete(runId);\n }\n };\n }\n\n /**\n * Subscribe to all events (server-side).\n */\n subscribeAll(callback: EventCallback): Unsubscribe {\n this.globalSubscribers.add(callback);\n return () => {\n this.globalSubscribers.delete(callback);\n };\n }\n\n /**\n * Close the transport.\n */\n close(): void {\n this.runSubscribers.clear();\n this.globalSubscribers.clear();\n this.endpoints.clear();\n }\n\n /**\n * Check if an event matches an endpoint's filters.\n */\n private matchesFilter(event: WorkflowEvent, endpoint: WebhookEndpoint): boolean {\n // Check event type filter\n if (endpoint.eventTypes && endpoint.eventTypes.length > 0) {\n if (!endpoint.eventTypes.includes(event.eventType)) {\n return false;\n }\n }\n\n // Check workflow kind filter\n if (endpoint.workflowKinds && endpoint.workflowKinds.length > 0) {\n if (!endpoint.workflowKinds.includes(event.kind)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Send a webhook with retry support.\n */\n private async sendWebhook(endpoint: WebhookEndpoint, event: WorkflowEvent): Promise<void> {\n const timeout = endpoint.timeout ?? this.defaultTimeout;\n const maxRetries = endpoint.retries ?? this.defaultRetries;\n\n const payload: WebhookPayload = {\n event: {\n ...event,\n timestamp: event.timestamp.toISOString(),\n },\n deliveredAt: new Date().toISOString(),\n webhookId: endpoint.id,\n };\n\n const body = JSON.stringify(payload);\n\n // Enforce payload size limit to prevent excessive memory use and network abuse\n const bodyBytes = Buffer.byteLength(body, 'utf8');\n if (bodyBytes > this.maxPayloadBytes) {\n throw new Error(\n `Webhook payload exceeds maximum size (${bodyBytes} bytes > ${this.maxPayloadBytes} bytes)`\n );\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...endpoint.headers,\n };\n\n // Sign payload if secret is provided\n if (endpoint.secret) {\n const signature = await this.signPayload(body, endpoint.secret);\n headers['X-Webhook-Signature'] = signature;\n }\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Resolve the hostname once and validate the resulting IP is not\n // private/reserved (SSRF protection). When the default fetch is used,\n // the resolved IP is then pinned for the connection so a hostile DNS\n // server cannot rebind the hostname to a different address between\n // validation and connect (M1: rebinding TOCTOU). The lookup runs\n // inside the retry loop so DNS failures are retried along with\n // network errors.\n const pinned = await this.validateResolvedHost(endpoint.url);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n let response;\n try {\n response = this.hasCustomFetch\n ? await this.fetchFn(endpoint.url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n redirect: 'error',\n })\n : await pinnedFetch(\n endpoint.url,\n { method: 'POST', headers, body, signal: controller.signal },\n pinned.address,\n pinned.family,\n );\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (response.ok) {\n return; // Success\n }\n\n lastError = new Error(`Webhook returned ${response.status}: ${response.statusText}`);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n }\n\n // Wait before retrying (except on last attempt)\n if (attempt < maxRetries) {\n await this.sleep(this.retryDelay * Math.pow(2, attempt));\n }\n }\n\n throw lastError;\n }\n\n /**\n * Sign a payload using HMAC-SHA256.\n */\n private async signPayload(payload: string, secret: string): Promise<string> {\n // Use Web Crypto API for HMAC-SHA256\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const data = encoder.encode(payload);\n\n const key = await crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hashArray = Array.from(new Uint8Array(signature));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n\n return `sha256=${hashHex}`;\n }\n\n /**\n * Sleep helper.\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n /**\n * Resolve a webhook URL's hostname exactly once, verify the resolved IP is\n * not private/reserved, and return the address + family so the caller can\n * pin the connection to it.\n *\n * Raw-IP URLs are validated and pinned too; they must not fall back to a\n * redirect-following HTTP client.\n *\n * Pinning the resolved IP closes the DNS rebinding TOCTOU window (audit M1):\n * with the original implementation `fetch()` re-resolved the hostname,\n * giving a hostile DNS server a chance to flip the answer to an internal\n * address between SSRF validation and connect.\n *\n * Uses a short-TTL cache and per-call timeout to avoid blocking the event loop.\n */\n private async validateResolvedHost(\n url: string,\n ): Promise<{ address: string; family: 4 | 6 }> {\n this.validateWebhookUrl(url);\n const parsed = new URL(url);\n const hostname = parsed.hostname.toLowerCase();\n\n // Raw IP literals need no DNS lookup, but still use the pinned HTTP path.\n // Node's URL parser wraps IPv6 literals in brackets (e.g., \"[2001:db8::1]\").\n const stripped = hostname.startsWith('[') ? hostname.slice(1, -1) : hostname;\n if (isIPv4(stripped) || isIPv6(stripped)) {\n return { address: stripped, family: isIPv6(stripped) ? 6 : 4 };\n }\n\n // Check cache first\n const cached = this.dnsCache.get(hostname);\n if (cached && cached.expiresAt > Date.now()) {\n if (isBlockedIp(cached.address)) {\n throw new Error(\n `Webhook URL hostname \"${hostname}\" resolves to blocked IP ${cached.address}`\n );\n }\n return { address: cached.address, family: cached.family };\n }\n\n try {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let address: string;\n let family: 4 | 6;\n try {\n // dns.lookup does not support AbortSignal. Bound delivery-slot occupancy\n // explicitly. The losing lookup remains observed and cannot populate the\n // cache or initiate a request after the deadline.\n const result = await Promise.race([\n dns.lookup(hostname),\n new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(\n `DNS lookup for webhook URL hostname \"${hostname}\" timed out`\n )), WebhookEventTransport.DNS_TIMEOUT_MS);\n }),\n ]);\n address = result.address;\n family = (result.family === 6 ? 6 : 4) as 4 | 6;\n } finally {\n clearTimeout(timeoutId);\n }\n\n // Cache the result\n this.dnsCache.set(hostname, {\n address,\n family,\n expiresAt: Date.now() + WebhookEventTransport.DNS_CACHE_TTL_MS,\n });\n\n if (isBlockedIp(address)) {\n throw new Error(\n `Webhook URL hostname \"${hostname}\" resolves to blocked IP ${address}`\n );\n }\n\n return { address, family };\n } catch (error) {\n if (error instanceof Error && error.message.includes('resolves to blocked IP')) {\n throw error;\n }\n throw new Error(`Failed to resolve webhook URL hostname \"${hostname}\": ${error}`);\n }\n }\n\n /**\n * Validate a webhook URL to prevent SSRF attacks.\n * Blocks non-HTTPS schemes (unless allowInsecureUrls is set),\n * and rejects private/reserved IP ranges and cloud metadata endpoints.\n */\n private validateWebhookUrl(url: string): void {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`Invalid webhook URL: ${url}`);\n }\n\n // Scheme check\n if (!this.allowInsecureUrls && parsed.protocol !== 'https:') {\n throw new Error(\n `Webhook URL must use HTTPS (got ${parsed.protocol}). Set allowInsecureUrls for development.`\n );\n }\n\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error(`Webhook URL must use HTTP(S) scheme (got ${parsed.protocol})`);\n }\n\n // Block dangerous hostnames\n const hostname = parsed.hostname.toLowerCase();\n if (isBlockedHost(hostname)) {\n throw new Error(`Webhook URL hostname is blocked (private/reserved): ${hostname}`);\n }\n }\n\n /**\n * Enqueue a webhook request with concurrency limiting and a queue-depth cap.\n * Returns `true` if the request was accepted (executed or queued), or\n * `false` if the queue is full and the request was rejected. The caller is\n * responsible for logging the drop and incrementing any drop metrics, since\n * it has the endpoint context.\n */\n private enqueueRequest(fn: () => Promise<void>, _endpointId: string): boolean {\n const execute = () => {\n this.activeRequests++;\n fn().finally(() => {\n this.activeRequests--;\n const next = this.requestQueue.shift();\n if (next) next();\n });\n };\n\n if (this.activeRequests < this.maxConcurrentRequests) {\n execute();\n return true;\n }\n\n if (this.maxQueueDepth > 0 && this.requestQueue.length >= this.maxQueueDepth) {\n return false;\n }\n\n this.requestQueue.push(execute);\n return true;\n }\n}\n\n/**\n * Resolve the effective `maxQueueDepth` from explicit config and the\n * `STEPFLOW_WEBHOOK_MAX_QUEUE_DEPTH` environment variable.\n *\n * Precedence: explicit config value > env var > default (1000).\n */\nfunction resolveMaxQueueDepth(configured: number | undefined): number {\n if (typeof configured === 'number') return configured;\n const env = typeof process !== 'undefined' ? process.env?.STEPFLOW_WEBHOOK_MAX_QUEUE_DEPTH : undefined;\n if (env !== undefined && env !== '') {\n const parsed = Number(env);\n if (Number.isFinite(parsed) && parsed >= 0) return parsed;\n }\n return 1000;\n}\n\n/**\n * Check if a hostname (as it appears in a URL) is blocked.\n * Handles bare hostnames, IPv4 literals, and bracketed IPv6 literals.\n */\nfunction isBlockedHost(hostname: string): boolean {\n if (hostname === 'localhost') return true;\n\n // IPv6 literals are wrapped in brackets by Node's URL parser, e.g. \"[::1]\".\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n const bare = hostname.slice(1, -1);\n return isIPv6(bare) ? isBlockedIPv6(bare) : true;\n }\n\n // Some callers (or legacy code paths) may pass an unwrapped IPv6 literal.\n if (isIPv6(hostname)) {\n return isBlockedIPv6(hostname);\n }\n\n if (isIPv4(hostname)) {\n return isBlockedIPv4(hostname);\n }\n\n return false;\n}\n\n/**\n * Check if a resolved IP address falls within a private/reserved range.\n * Used for runtime DNS resolution validation to prevent SSRF via DNS rebinding.\n */\nfunction isBlockedIp(ip: string): boolean {\n if (isIPv4(ip)) return isBlockedIPv4(ip);\n if (isIPv6(ip)) return isBlockedIPv6(ip);\n return false;\n}\n\n/**\n * Block IPv4 addresses in private, loopback, link-local, and cloud-metadata ranges.\n */\nfunction isBlockedIPv4(ip: string): boolean {\n const match = ip.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (!match) return false;\n const [, a, b] = match.map(Number);\n return isBlockedIPv4Octets(a, b);\n}\n\nfunction isBlockedIPv4Octets(a: number, b: number): boolean {\n // 0.0.0.0/8 — \"this network\"\n if (a === 0) return true;\n // 10.0.0.0/8 — RFC 1918 private\n if (a === 10) return true;\n // 100.64.0.0/10 — CGNAT\n if (a === 100 && b >= 64 && b <= 127) return true;\n // 127.0.0.0/8 — loopback\n if (a === 127) return true;\n // 169.254.0.0/16 — link-local (covers cloud metadata 169.254.169.254)\n if (a === 169 && b === 254) return true;\n // 172.16.0.0/12 — RFC 1918 private\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16 — RFC 1918 private\n if (a === 192 && b === 168) return true;\n // 224.0.0.0/4 multicast and 240.0.0.0/4 reserved (incl. 255.255.255.255 broadcast)\n if (a >= 224) return true;\n return false;\n}\n\n/**\n * Block IPv6 addresses in reserved, private, loopback, link-local, IPv4-mapped,\n * IPv4-translated, 6to4, and Teredo ranges. For mapped/embedded IPv4 forms,\n * the embedded IPv4 is decoded and rechecked against the IPv4 blocklist.\n */\nfunction isBlockedIPv6(ip: string): boolean {\n const groups = expandIPv6(ip);\n if (!groups) return false;\n\n // ::1 loopback and :: unspecified\n const allButLastZero = groups.slice(0, 7).every(g => g === 0);\n if (allButLastZero && groups[7] === 1) return true;\n if (groups.every(g => g === 0)) return true;\n\n // ::ffff:0:0/96 — IPv4-mapped IPv6 (decode embedded IPv4)\n if (groups.slice(0, 5).every(g => g === 0) && groups[5] === 0xffff) {\n return isBlockedIPv4Octets((groups[6] >> 8) & 0xff, groups[6] & 0xff);\n }\n\n // 64:ff9b::/96 — IPv4/IPv6 translation (RFC 6052) and 64:ff9b:1::/48 (RFC 8215)\n if (groups[0] === 0x64 && groups[1] === 0xff9b && groups.slice(2, 6).every(g => g === 0)) {\n return isBlockedIPv4Octets((groups[6] >> 8) & 0xff, groups[6] & 0xff);\n }\n\n // 2002::/16 — 6to4 (next 32 bits are the embedded IPv4)\n if (groups[0] === 0x2002) {\n return isBlockedIPv4Octets((groups[1] >> 8) & 0xff, groups[1] & 0xff);\n }\n\n // 2001::/32 — Teredo. Block conservatively; embedded server/client v4 fields\n // can mask private destinations and Teredo isn't a normal webhook target.\n if (groups[0] === 0x2001 && groups[1] === 0) return true;\n\n // 100::/64 — RFC 6666 Discard prefix\n if (groups[0] === 0x0100 && groups.slice(1, 4).every(g => g === 0)) return true;\n\n // fc00::/7 — Unique Local Addresses (fc00:: through fdff::)\n if ((groups[0] & 0xfe00) === 0xfc00) return true;\n\n // fe80::/10 — Link-local (fe80:: through febf::)\n if ((groups[0] & 0xffc0) === 0xfe80) return true;\n\n // ff00::/8 — Multicast\n if ((groups[0] & 0xff00) === 0xff00) return true;\n\n return false;\n}\n\n/**\n * Expand an IPv6 string into an array of 8 numeric hextets (0–0xffff).\n * Handles \"::\" abbreviation, embedded IPv4 dotted-quad, and zone IDs.\n * Returns null if the input is not a valid IPv6 address.\n */\nfunction expandIPv6(addr: string): number[] | null {\n // Strip zone identifier (e.g., \"fe80::1%eth0\")\n const noZone = addr.split('%')[0];\n if (!isIPv6(noZone)) return null;\n\n let normalized = noZone.toLowerCase();\n\n // Convert trailing dotted-quad (e.g., \"::ffff:192.168.0.1\") into two hextets.\n const v4Tail = normalized.match(/^(.*:)(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (v4Tail) {\n const a = parseInt(v4Tail[2], 10);\n const b = parseInt(v4Tail[3], 10);\n const c = parseInt(v4Tail[4], 10);\n const d = parseInt(v4Tail[5], 10);\n if ([a, b, c, d].some(n => n > 255)) return null;\n const hi = ((a << 8) | b).toString(16);\n const lo = ((c << 8) | d).toString(16);\n normalized = `${v4Tail[1]}${hi}:${lo}`;\n }\n\n const halves = normalized.split('::');\n let parts: string[];\n if (halves.length === 1) {\n parts = halves[0].split(':');\n if (parts.length !== 8) return null;\n } else if (halves.length === 2) {\n const head = halves[0] ? halves[0].split(':') : [];\n const tail = halves[1] ? halves[1].split(':') : [];\n const missing = 8 - head.length - tail.length;\n if (missing < 0) return null;\n parts = [...head, ...Array(missing).fill('0'), ...tail];\n } else {\n return null;\n }\n\n const groups: number[] = [];\n for (const part of parts) {\n if (!/^[0-9a-f]{1,4}$/.test(part)) return null;\n groups.push(parseInt(part, 16));\n }\n return groups;\n}\n","/**\n * DNS-pinned fetch helper.\n *\n * Performs an HTTP(S) request that connects to a caller-supplied IP address\n * instead of re-resolving the URL hostname. Used by the webhook transport to\n * close the SSRF DNS-rebinding TOCTOU window: the hostname is resolved\n * exactly once (by the caller), the resolved IP is validated, and the\n * resolved IP is then pinned for the connection so the request cannot be\n * rerouted by a hostile DNS server.\n *\n * The original hostname is preserved for TLS SNI / certificate verification\n * and for the HTTP `Host` header.\n *\n * Audit: 2026-04-27 finding M1 — DNS TOCTOU between SSRF validation and\n * connect.\n */\n\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport { isIP } from 'node:net';\nimport type { LookupAddress, LookupOptions } from 'node:dns';\n\n/** Minimal fetch-compatible Response surface used by the webhook transport. */\nexport interface PinnedFetchResponse {\n ok: boolean;\n status: number;\n statusText: string;\n}\n\n/** Subset of `RequestInit` accepted by `pinnedFetch`. */\nexport interface PinnedFetchInit {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n signal?: AbortSignal;\n}\n\n/**\n * Make an HTTP(S) request that connects to `address` instead of resolving\n * `url`'s hostname. Returns a minimal `Response`-shaped object compatible\n * with the webhook transport's status / ok checks.\n *\n * @throws Error on transport failure or abort\n */\nexport function pinnedFetch(\n url: string,\n init: PinnedFetchInit,\n address: string,\n family: 4 | 6 = 4,\n): Promise<PinnedFetchResponse> {\n const parsed = new URL(url);\n const hostname = parsed.hostname.replace(/^\\[|\\]$/g, '');\n const isHttps = parsed.protocol === 'https:';\n const lib = isHttps ? https : http;\n const port = parsed.port ? Number(parsed.port) : isHttps ? 443 : 80;\n\n // `lookup` is called by the agent's socket-create path. By returning the\n // pre-validated address here we guarantee the TCP connection target is the\n // IP whose safety the caller already verified. Node may invoke this with\n // `{ all: true }` (expects an array result) or single-address mode\n // (expects positional address/family) — handle both for cross-version safety.\n const lookup = (\n _hostname: string,\n options: LookupOptions,\n cb: (err: NodeJS.ErrnoException | null, addr: string | LookupAddress[], fam?: number) => void,\n ): void => {\n if (options && options.all === true) {\n cb(null, [{ address, family }]);\n } else {\n cb(null, address, family);\n }\n };\n\n return new Promise<PinnedFetchResponse>((resolve, reject) => {\n const req = lib.request(\n {\n hostname,\n port,\n path: `${parsed.pathname || '/'}${parsed.search}`,\n method: init.method ?? 'GET',\n headers: init.headers,\n lookup,\n servername: isIP(hostname) ? '' : hostname,\n signal: init.signal,\n },\n (res) => {\n // Drain the body — we only inspect status, but leaving the socket\n // half-read would leak it.\n res.resume();\n res.on('end', () => {\n const status = res.statusCode ?? 0;\n resolve({\n ok: status >= 200 && status < 300,\n status,\n statusText: res.statusMessage ?? '',\n });\n });\n res.on('error', reject);\n },\n );\n\n req.on('error', reject);\n\n if (init.body !== undefined) {\n req.write(init.body);\n }\n req.end();\n });\n}\n","/**\n * Helpers for signing and verifying inbound / outbound webhook payloads.\n *\n * Both sides use HMAC-SHA256 with a `sha256=<lowercase-hex>` envelope —\n * the same shape produced by `WebhookEventTransport.signPayload`, matching\n * common third-party providers (Stripe, GitHub, etc.).\n *\n * Audit: 2026-04-27 finding L2 — without a shipped verifier, consumers\n * receiving inbound webhooks tend to roll their own comparisons that are\n * vulnerable to timing attacks.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/**\n * Compute the canonical `X-Webhook-Signature` value for a request body.\n *\n * Returns a string of the form `sha256=<lowercase-hex>`.\n */\nexport function signWebhookPayload(body: string | Uint8Array, secret: string): string {\n const hmac = createHmac('sha256', secret);\n hmac.update(body);\n return `sha256=${hmac.digest('hex')}`;\n}\n\n/**\n * Constant-time verification of an inbound webhook signature.\n *\n * @param body The raw request body that was signed.\n * @param signature The header value sent by the producer (e.g. `sha256=…`).\n * @param secret The shared HMAC secret.\n * @returns `true` only if the computed digest matches the supplied signature.\n *\n * Properties:\n * - Accepts an optional `sha256=` prefix on the signature, but does not\n * require it (mirrors common third-party header conventions).\n * - Uses `timingSafeEqual` so an attacker cannot probe digit-by-digit.\n * - Returns `false` for missing/empty values, non-hex input, and length\n * mismatches without throwing.\n * - Returns `false` if the secret is empty — an empty secret is almost\n * always a misconfiguration we do not want to silently accept.\n */\nexport function verifyWebhookSignature(\n body: string | Uint8Array,\n signature: string | undefined | null,\n secret: string,\n): boolean {\n if (!secret || typeof signature !== 'string' || signature.length === 0) {\n return false;\n }\n\n const provided = signature.startsWith('sha256=')\n ? signature.slice('sha256='.length)\n : signature;\n\n // Hex-encoded SHA-256 is always exactly 64 characters; reject anything\n // else so we don't mistake other algorithms or junk strings for ours.\n if (!/^[0-9a-f]+$/i.test(provided)) {\n return false;\n }\n\n const expectedHex = signWebhookPayload(body, secret).slice('sha256='.length);\n\n // Length mismatch — `timingSafeEqual` would throw; exposing the digest\n // length is fine since it's a public property of SHA-256.\n if (provided.length !== expectedHex.length) {\n return false;\n }\n\n const a = Buffer.from(provided.toLowerCase(), 'hex');\n const b = Buffer.from(expectedHex, 'hex');\n if (a.length !== b.length || a.length === 0) return false;\n return timingSafeEqual(a, b);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -529,10 +529,8 @@ var PostgresSchedulePersistence = class {
|
|
|
529
529
|
* Only closes the pool if it was created by this adapter.
|
|
530
530
|
*/
|
|
531
531
|
async close() {
|
|
532
|
-
await this.db.destroy();
|
|
533
|
-
|
|
534
|
-
await this.pool.end();
|
|
535
|
-
}
|
|
532
|
+
if (this.db && this.ownsPool) await this.db.destroy();
|
|
533
|
+
this.initialized = false;
|
|
536
534
|
}
|
|
537
535
|
// ============================================================================
|
|
538
536
|
// Database Initialization
|
|
@@ -724,4 +722,4 @@ export {
|
|
|
724
722
|
SQLiteSchedulePersistence,
|
|
725
723
|
PostgresSchedulePersistence
|
|
726
724
|
};
|
|
727
|
-
//# sourceMappingURL=chunk-
|
|
725
|
+
//# sourceMappingURL=chunk-TVSADUYU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scheduler/cron.ts","../src/scheduler/sqlite-persistence.ts","../src/scheduler/postgres-persistence.ts"],"sourcesContent":["/**\n * CronScheduler - Scheduler implementation using cron expressions.\n *\n * Provides time-based and workflow-completion-based triggers for workflows.\n */\n\nimport { CronExpressionParser } from 'cron-parser';\nimport type { WorkflowKind, RunStatus, Logger } from '../core/types';\nimport type { WorkflowEngine } from '../core/engine';\nimport type { Scheduler, WorkflowSchedule, TriggerType } from './types';\nimport type { WorkflowEvent } from '../events/types';\nimport { generateId } from '../utils/id';\nimport { ConsoleLogger } from '../utils/logger';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for the CronScheduler.\n */\nexport interface CronSchedulerConfig {\n /** The workflow engine instance */\n engine: WorkflowEngine;\n\n /** Logger instance */\n logger?: Logger;\n\n /** Poll interval for checking schedules (ms, default: 1000) */\n pollInterval?: number;\n\n /** Optional persistence adapter for schedules */\n persistence?: SchedulePersistence;\n}\n\n/**\n * Interface for schedule persistence.\n * Implement this to persist schedules to a database.\n */\nexport interface SchedulePersistence {\n /** Load all schedules from storage */\n loadSchedules(): Promise<WorkflowSchedule[]>;\n\n /** Save a schedule */\n saveSchedule(schedule: WorkflowSchedule): Promise<void>;\n\n /** Update a schedule */\n updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void>;\n\n /** Delete a schedule */\n deleteSchedule(scheduleId: string): Promise<void>;\n}\n\n// ============================================================================\n// CronScheduler Class\n// ============================================================================\n\n/**\n * Scheduler implementation that supports cron expressions and workflow completion triggers.\n *\n * @example\n * ```typescript\n * const scheduler = new CronScheduler({\n * engine,\n * pollInterval: 1000,\n * });\n *\n * // Add a cron schedule (every day at midnight)\n * await scheduler.addSchedule({\n * workflowKind: 'cleanup.daily',\n * triggerType: 'cron',\n * cronExpression: '0 0 * * *',\n * enabled: true,\n * });\n *\n * // Add a workflow completion trigger\n * await scheduler.addSchedule({\n * workflowKind: 'notification.send',\n * triggerType: 'workflow_completed',\n * triggerOnWorkflowKind: 'order.process',\n * triggerOnStatus: ['succeeded'],\n * enabled: true,\n * });\n *\n * await scheduler.start();\n * ```\n */\nexport class CronScheduler implements Scheduler {\n private engine: WorkflowEngine;\n private logger: Logger;\n private pollInterval: number;\n private persistence?: SchedulePersistence;\n\n private schedules = new Map<string, WorkflowSchedule>();\n private running = false;\n private pollTimer: ReturnType<typeof setInterval> | null = null;\n private eventUnsubscribe: (() => void) | null = null;\n\n constructor(config: CronSchedulerConfig) {\n this.engine = config.engine;\n this.logger = config.logger ?? new ConsoleLogger();\n this.pollInterval = config.pollInterval ?? 1000;\n this.persistence = config.persistence;\n }\n\n // ============================================================================\n // Scheduler Interface Implementation\n // ============================================================================\n\n /**\n * Start the scheduler.\n * Begins polling for cron schedules and subscribes to workflow completion events.\n */\n async start(): Promise<void> {\n if (this.running) {\n this.logger.warn('Scheduler is already running');\n return;\n }\n\n // Clean up any leftover subscriptions from a previous start/stop cycle\n if (this.eventUnsubscribe) {\n this.eventUnsubscribe();\n this.eventUnsubscribe = null;\n }\n if (this.pollTimer) {\n clearInterval(this.pollTimer);\n this.pollTimer = null;\n }\n\n this.logger.info('Starting scheduler...');\n\n // Load schedules from persistence if available\n if (this.persistence) {\n const loaded = await this.persistence.loadSchedules();\n for (const schedule of loaded) {\n this.schedules.set(schedule.id, schedule);\n }\n this.logger.info(`Loaded ${loaded.length} schedules from persistence`);\n }\n\n // Calculate next run times for cron schedules.\n // Wrap each schedule in try-catch so one invalid persisted cron expression\n // doesn't prevent the rest from loading.\n for (const schedule of this.schedules.values()) {\n if (schedule.triggerType === 'cron' && schedule.cronExpression) {\n try {\n CronExpressionParser.parse(schedule.cronExpression, {\n tz: schedule.timezone,\n });\n this.updateNextRunTime(schedule);\n } catch (error) {\n this.logger.error(\n `Invalid cron expression \"${schedule.cronExpression}\" for schedule ${schedule.id}, disabling schedule:`,\n error\n );\n schedule.enabled = false;\n schedule.nextRunAt = undefined;\n }\n }\n }\n\n // Start polling for cron schedules\n this.pollTimer = setInterval(() => this.checkSchedules(), this.pollInterval);\n\n // Subscribe to workflow completion events\n this.eventUnsubscribe = this.engine.subscribeToAll((event) => {\n this.handleWorkflowEvent(event);\n });\n\n this.running = true;\n this.logger.info('Scheduler started');\n }\n\n /**\n * Stop the scheduler.\n * Stops polling and unsubscribes from events.\n */\n async stop(): Promise<void> {\n if (!this.running) {\n return;\n }\n\n this.logger.info('Stopping scheduler...');\n\n if (this.pollTimer) {\n clearInterval(this.pollTimer);\n this.pollTimer = null;\n }\n\n if (this.eventUnsubscribe) {\n this.eventUnsubscribe();\n this.eventUnsubscribe = null;\n }\n\n this.running = false;\n this.logger.info('Scheduler stopped');\n }\n\n /**\n * Add a new schedule.\n */\n async addSchedule(scheduleData: Omit<WorkflowSchedule, 'id'>): Promise<WorkflowSchedule> {\n const schedule: WorkflowSchedule = {\n ...scheduleData,\n id: generateId(),\n };\n\n // Validate cron expression if provided\n if (schedule.triggerType === 'cron' && schedule.cronExpression) {\n try {\n CronExpressionParser.parse(schedule.cronExpression, {\n tz: schedule.timezone,\n });\n } catch (error) {\n throw new Error(`Invalid cron expression: ${schedule.cronExpression}`);\n }\n\n // Calculate next run time\n this.updateNextRunTime(schedule);\n }\n\n // Validate workflow completion trigger\n if (schedule.triggerType === 'workflow_completed') {\n if (!schedule.triggerOnWorkflowKind) {\n throw new Error('triggerOnWorkflowKind is required for workflow_completed triggers');\n }\n }\n\n this.schedules.set(schedule.id, schedule);\n\n // Persist if available\n if (this.persistence) {\n await this.persistence.saveSchedule(schedule);\n }\n\n this.logger.info(`Added schedule: ${schedule.id} (${schedule.workflowKind})`);\n\n return schedule;\n }\n\n /**\n * Remove a schedule.\n */\n async removeSchedule(scheduleId: string): Promise<void> {\n if (!this.schedules.has(scheduleId)) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n this.schedules.delete(scheduleId);\n\n if (this.persistence) {\n await this.persistence.deleteSchedule(scheduleId);\n }\n\n this.logger.info(`Removed schedule: ${scheduleId}`);\n }\n\n /**\n * Update a schedule.\n */\n async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {\n const schedule = this.schedules.get(scheduleId);\n if (!schedule) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n // Don't allow changing the ID\n delete updates.id;\n\n // Update the schedule\n Object.assign(schedule, updates);\n\n // Revalidate and update next run time if cron expression changed\n if (updates.cronExpression !== undefined || updates.timezone !== undefined) {\n if (schedule.cronExpression) {\n try {\n CronExpressionParser.parse(schedule.cronExpression, {\n tz: schedule.timezone,\n });\n this.updateNextRunTime(schedule);\n } catch (error) {\n throw new Error(`Invalid cron expression: ${schedule.cronExpression}`);\n }\n }\n }\n\n if (this.persistence) {\n await this.persistence.updateSchedule(scheduleId, updates);\n }\n\n this.logger.debug(`Updated schedule: ${scheduleId}`);\n }\n\n /**\n * Get all schedules.\n */\n async getSchedules(): Promise<WorkflowSchedule[]> {\n return Array.from(this.schedules.values());\n }\n\n /**\n * Get a schedule by ID.\n */\n getSchedule(scheduleId: string): WorkflowSchedule | undefined {\n return this.schedules.get(scheduleId);\n }\n\n /**\n * Manually trigger a scheduled workflow.\n */\n async triggerNow(scheduleId: string): Promise<string> {\n const schedule = this.schedules.get(scheduleId);\n if (!schedule) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n return this.executeSchedule(schedule);\n }\n\n // ============================================================================\n // Internal Methods\n // ============================================================================\n\n /**\n * Check all cron schedules and execute those that are due.\n */\n private checkSchedules(): void {\n const now = new Date();\n\n for (const schedule of this.schedules.values()) {\n if (!schedule.enabled) continue;\n if (schedule.triggerType !== 'cron') continue;\n if (!schedule.nextRunAt) continue;\n\n if (schedule.nextRunAt <= now) {\n // Execute the schedule\n this.executeSchedule(schedule).catch((error) => {\n this.logger.error(`Failed to execute schedule ${schedule.id}:`, error);\n });\n\n // Update next run time\n this.updateNextRunTime(schedule);\n }\n }\n }\n\n /**\n * Handle workflow events for completion triggers.\n */\n private handleWorkflowEvent(event: WorkflowEvent): void {\n // Only handle run completion events\n // Note: orchestrator emits 'run.completed' for success, 'run.failed' for failure\n if (event.eventType !== 'run.completed' && event.eventType !== 'run.failed') {\n return;\n }\n\n const completedStatus: RunStatus = event.eventType === 'run.completed' ? 'succeeded' : 'failed';\n const completedKind = event.kind;\n\n // Find schedules triggered by this workflow completion\n for (const schedule of this.schedules.values()) {\n if (!schedule.enabled) continue;\n if (schedule.triggerType !== 'workflow_completed') continue;\n if (schedule.triggerOnWorkflowKind !== completedKind) continue;\n\n // Check if the status matches\n if (schedule.triggerOnStatus && !schedule.triggerOnStatus.includes(completedStatus)) {\n continue;\n }\n\n // Execute the schedule\n this.executeSchedule(schedule, {\n triggerRunId: event.runId,\n triggerStatus: completedStatus,\n }).catch((error) => {\n this.logger.error(`Failed to execute schedule ${schedule.id}:`, error);\n });\n }\n }\n\n /**\n * Execute a schedule by starting the workflow.\n */\n private async executeSchedule(\n schedule: WorkflowSchedule,\n triggerContext?: { triggerRunId?: string; triggerStatus?: RunStatus }\n ): Promise<string> {\n this.logger.info(`Executing schedule: ${schedule.id} (${schedule.workflowKind})`);\n\n // Build metadata with trigger info\n const metadata = {\n ...schedule.metadata,\n scheduleId: schedule.id,\n triggerType: schedule.triggerType,\n ...(triggerContext ?? {}),\n };\n\n // Start the workflow\n const runId = await this.engine.startRun({\n kind: schedule.workflowKind,\n input: schedule.input,\n metadata,\n });\n\n // Update schedule state\n schedule.lastRunAt = new Date();\n schedule.lastRunId = runId;\n\n if (this.persistence) {\n await this.persistence.updateSchedule(schedule.id, {\n lastRunAt: schedule.lastRunAt,\n lastRunId: schedule.lastRunId,\n nextRunAt: schedule.nextRunAt,\n });\n }\n\n return runId;\n }\n\n /**\n * Update the next run time for a cron schedule.\n */\n private updateNextRunTime(schedule: WorkflowSchedule): void {\n if (!schedule.cronExpression) return;\n\n try {\n const interval = CronExpressionParser.parse(schedule.cronExpression, {\n currentDate: new Date(),\n tz: schedule.timezone,\n });\n\n schedule.nextRunAt = interval.next().toDate();\n } catch (error) {\n this.logger.error(`Failed to parse cron expression for schedule ${schedule.id}:`, error);\n schedule.nextRunAt = undefined;\n }\n }\n}\n","/**\n * SQLite persistence adapter for schedules.\n *\n * Stores workflow schedules in a SQLite database table.\n */\n\nimport type { Database, Statement } from 'better-sqlite3';\nimport type { WorkflowSchedule } from './types';\nimport type { SchedulePersistence } from './cron';\nimport type { Logger, RunStatus } from '../core/types';\nimport { generateId } from '../utils/id';\nimport { safeJsonParse as sharedSafeJsonParse } from '../utils/safe-json';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for SQLiteSchedulePersistence.\n */\nexport interface SQLiteSchedulePersistenceConfig {\n /** SQLite database instance */\n db: Database;\n\n /** Table name for schedules (default: workflow_schedules) */\n tableName?: string;\n\n /**\n * Optional structured logger used to surface JSON corruption events.\n * Without one, parse failures still increment the corruption counter\n * exposed via `getJsonParseCorruptionCount()` but produce no log output.\n */\n logger?: Logger;\n}\n\n// ============================================================================\n// SQLiteSchedulePersistence Class\n// ============================================================================\n\n/**\n * SQLite-based persistence for workflow schedules.\n *\n * @example\n * ```typescript\n * import Database from 'better-sqlite3';\n *\n * const db = new Database('workflow.db');\n * const persistence = new SQLiteSchedulePersistence({ db });\n *\n * const scheduler = new CronScheduler({\n * engine,\n * persistence,\n * });\n * ```\n */\nexport class SQLiteSchedulePersistence implements SchedulePersistence {\n private db: Database;\n private tableName: string;\n private logger: Logger | undefined;\n private stmts: {\n insert: Statement;\n update: Statement;\n delete: Statement;\n getAll: Statement;\n getById: Statement;\n } | null = null;\n\n constructor(config: SQLiteSchedulePersistenceConfig) {\n this.db = config.db;\n this.tableName = config.tableName ?? 'workflow_schedules';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(this.tableName)) {\n throw new Error(`Invalid table name: ${this.tableName}`);\n }\n this.logger = config.logger;\n this.initializeDatabase();\n }\n\n // ============================================================================\n // Database Initialization\n // ============================================================================\n\n private initializeDatabase(): void {\n // Create schedules table.\n // Schema layout:\n // id — unique schedule identifier (TEXT PK)\n // workflow_kind — target workflow type to spawn\n // trigger_type — 'cron', 'workflow_completed', or 'manual'\n // cron_expression — cron expression for time-based triggers (nullable)\n // timezone — IANA timezone for cron evaluation (nullable)\n // trigger_on_* — fields for workflow-completion triggers\n // input / metadata — JSON-serialized payloads for spawned runs\n // enabled — whether this schedule is active (0/1)\n // last_run_at/id — most recent execution tracking\n // next_run_at — next scheduled execution time\n // created_at/updated_at — audit timestamps\n // No migration strategy is currently implemented; table is created idempotently.\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n id TEXT PRIMARY KEY,\n workflow_kind TEXT NOT NULL,\n trigger_type TEXT NOT NULL,\n cron_expression TEXT,\n timezone TEXT,\n trigger_on_workflow_kind TEXT,\n trigger_on_status TEXT,\n input TEXT,\n metadata TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_run_at TEXT,\n last_run_id TEXT,\n next_run_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `);\n\n // Create indexes\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_workflow_kind\n ON ${this.tableName}(workflow_kind);\n\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_enabled\n ON ${this.tableName}(enabled);\n\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_trigger_type\n ON ${this.tableName}(trigger_type);\n `);\n\n // Prepare statements\n this.stmts = {\n insert: this.db.prepare(`\n INSERT INTO ${this.tableName} (\n id, workflow_kind, trigger_type, cron_expression, timezone,\n trigger_on_workflow_kind, trigger_on_status, input, metadata,\n enabled, last_run_at, last_run_id, next_run_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `),\n update: this.db.prepare(`\n UPDATE ${this.tableName}\n SET workflow_kind = COALESCE(?, workflow_kind),\n trigger_type = COALESCE(?, trigger_type),\n cron_expression = ?,\n timezone = ?,\n trigger_on_workflow_kind = ?,\n trigger_on_status = ?,\n input = ?,\n metadata = ?,\n enabled = COALESCE(?, enabled),\n last_run_at = ?,\n last_run_id = ?,\n next_run_at = ?,\n updated_at = datetime('now')\n WHERE id = ?\n `),\n delete: this.db.prepare(`\n DELETE FROM ${this.tableName} WHERE id = ?\n `),\n getAll: this.db.prepare(`\n SELECT * FROM ${this.tableName}\n `),\n getById: this.db.prepare(`\n SELECT * FROM ${this.tableName} WHERE id = ?\n `),\n };\n }\n\n // ============================================================================\n // SchedulePersistence Interface Implementation\n // ============================================================================\n\n async loadSchedules(): Promise<WorkflowSchedule[]> {\n const rows = this.stmts!.getAll.all() as ScheduleRow[];\n return rows.map(row => this.rowToSchedule(row));\n }\n\n async saveSchedule(schedule: WorkflowSchedule): Promise<void> {\n this.stmts!.insert.run(\n schedule.id,\n schedule.workflowKind,\n schedule.triggerType,\n schedule.cronExpression ?? null,\n schedule.timezone ?? null,\n schedule.triggerOnWorkflowKind ?? null,\n schedule.triggerOnStatus ? JSON.stringify(schedule.triggerOnStatus) : null,\n schedule.input ? JSON.stringify(schedule.input) : null,\n schedule.metadata ? JSON.stringify(schedule.metadata) : null,\n schedule.enabled ? 1 : 0,\n schedule.lastRunAt?.toISOString() ?? null,\n schedule.lastRunId ?? null,\n schedule.nextRunAt?.toISOString() ?? null\n );\n }\n\n async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {\n // Get existing schedule to merge with updates\n const existing = this.stmts!.getById.get(scheduleId) as ScheduleRow | undefined;\n if (!existing) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n const merged = {\n ...this.rowToSchedule(existing),\n ...updates,\n };\n\n this.stmts!.update.run(\n updates.workflowKind ?? null,\n updates.triggerType ?? null,\n merged.cronExpression ?? null,\n merged.timezone ?? null,\n merged.triggerOnWorkflowKind ?? null,\n merged.triggerOnStatus ? JSON.stringify(merged.triggerOnStatus) : null,\n merged.input ? JSON.stringify(merged.input) : null,\n merged.metadata ? JSON.stringify(merged.metadata) : null,\n updates.enabled !== undefined ? (updates.enabled ? 1 : 0) : null,\n merged.lastRunAt?.toISOString() ?? null,\n merged.lastRunId ?? null,\n merged.nextRunAt?.toISOString() ?? null,\n scheduleId\n );\n }\n\n async deleteSchedule(scheduleId: string): Promise<void> {\n this.stmts!.delete.run(scheduleId);\n }\n\n // ============================================================================\n // Helper Methods\n // ============================================================================\n\n private safeJsonParse(\n json: string,\n fallback: unknown,\n rowId: string,\n column: string,\n ): unknown {\n return sharedSafeJsonParse(json, fallback, {\n component: 'SQLiteSchedulePersistence',\n rowId,\n column,\n logger: this.logger,\n });\n }\n\n private rowToSchedule(row: ScheduleRow): WorkflowSchedule {\n return {\n id: row.id,\n workflowKind: row.workflow_kind,\n triggerType: row.trigger_type as WorkflowSchedule['triggerType'],\n cronExpression: row.cron_expression ?? undefined,\n timezone: row.timezone ?? undefined,\n triggerOnWorkflowKind: row.trigger_on_workflow_kind ?? undefined,\n triggerOnStatus: row.trigger_on_status\n ? this.safeJsonParse(row.trigger_on_status, undefined, row.id, 'trigger_on_status') as RunStatus[] | undefined\n : undefined,\n input: row.input ? this.safeJsonParse(row.input, undefined, row.id, 'input') as Record<string, unknown> | undefined : undefined,\n metadata: row.metadata ? this.safeJsonParse(row.metadata, undefined, row.id, 'metadata') as Record<string, unknown> | undefined : undefined,\n enabled: row.enabled === 1,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : undefined,\n lastRunId: row.last_run_id ?? undefined,\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : undefined,\n };\n }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface ScheduleRow {\n id: string;\n workflow_kind: string;\n trigger_type: string;\n cron_expression: string | null;\n timezone: string | null;\n trigger_on_workflow_kind: string | null;\n trigger_on_status: string | null;\n input: string | null;\n metadata: string | null;\n enabled: number;\n last_run_at: string | null;\n last_run_id: string | null;\n next_run_at: string | null;\n created_at: string;\n updated_at: string;\n}\n","/**\n * PostgreSQL persistence adapter for schedules.\n *\n * Stores workflow schedules in a PostgreSQL database table using Kysely.\n */\n\nimport type { Kysely as KyselyType } from 'kysely';\nimport type { Pool, PoolConfig } from 'pg';\nimport { loadPostgresDeps } from '../utils/postgres-deps.js';\n\n// Lazy-loaded dependencies - populated by loadPostgresDeps() during initialize()\nlet Kysely: any;\nlet PostgresDialect: any;\nlet sql: any;\nlet pgModule: any;\nimport type { WorkflowSchedule } from './types.js';\nimport type { SchedulePersistence } from './cron.js';\nimport type { Logger, RunStatus } from '../core/types.js';\nimport { safeJsonParse as sharedSafeJsonParse } from '../utils/safe-json.js';\n\n// ============================================================================\n// Database Types (Kysely schema)\n// ============================================================================\n\n/** Kysely row type for the workflow_schedules table. */\ninterface WorkflowSchedulesTable {\n id: string;\n /** Workflow kind this schedule targets. */\n workflow_kind: string;\n /** Trigger type: 'cron', 'workflow_completed', or 'manual'. */\n trigger_type: string;\n /** Cron expression for time-based triggers, null for non-cron schedules. */\n cron_expression: string | null;\n /** IANA timezone for cron evaluation, null defaults to UTC. */\n timezone: string | null;\n /** Workflow kind that triggers on completion, null for non-completion triggers. */\n trigger_on_workflow_kind: string | null;\n /** JSON-serialized array of RunStatus values that activate the trigger. */\n trigger_on_status: string | null;\n /** JSON-serialized input payload for spawned runs. */\n input_json: string | null;\n /** JSON-serialized arbitrary metadata for spawned runs. */\n metadata_json: string | null;\n /** Whether this schedule is active. */\n enabled: boolean;\n /** Timestamp of the most recent run spawned by this schedule. */\n last_run_at: Date | null;\n /** Run ID of the most recent run spawned by this schedule. */\n last_run_id: string | null;\n /** Next scheduled execution time. */\n next_run_at: Date | null;\n created_at: Date;\n updated_at: Date;\n}\n\ninterface SchedulesDatabase {\n workflow_schedules: WorkflowSchedulesTable;\n}\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for PostgresSchedulePersistence.\n */\nexport interface PostgresSchedulePersistenceConfig {\n /**\n * PostgreSQL connection string.\n * Example: \"postgresql://user:pass@localhost:5432/dbname\"\n */\n connectionString?: string;\n\n /**\n * Existing pg.Pool instance for connection sharing with application.\n * If provided, the adapter will not close this pool on close().\n */\n pool?: Pool;\n\n /**\n * Pool configuration options (if not providing pool or connectionString).\n */\n poolConfig?: PoolConfig;\n\n /**\n * Schema name for the schedules table.\n * @default 'public'\n */\n schema?: string;\n\n /**\n * Table name for schedules.\n * @default 'workflow_schedules'\n */\n tableName?: string;\n\n /**\n * Automatically create tables on initialization.\n * @default true\n */\n autoMigrate?: boolean;\n\n /**\n * Optional structured logger used to surface JSON corruption events.\n * Without one, parse failures still increment the corruption counter\n * exposed via `getJsonParseCorruptionCount()` but produce no log output.\n */\n logger?: Logger;\n}\n\n// ============================================================================\n// PostgresSchedulePersistence Class\n// ============================================================================\n\n/**\n * PostgreSQL-based persistence for workflow schedules.\n *\n * @example\n * ```typescript\n * const persistence = new PostgresSchedulePersistence({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * await persistence.initialize();\n *\n * const scheduler = new CronScheduler({\n * engine,\n * persistence,\n * });\n * ```\n *\n * @example Sharing connection pool\n * ```typescript\n * import pg from 'pg';\n *\n * const pool = new pg.Pool({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const persistence = new PostgresSchedulePersistence({ pool });\n * await persistence.initialize();\n * ```\n */\nexport class PostgresSchedulePersistence implements SchedulePersistence {\n private db!: KyselyType<SchedulesDatabase>;\n private pool!: Pool;\n private ownsPool = false;\n private schema: string;\n private tableName: string;\n private autoMigrate: boolean;\n private initialized = false;\n private config: PostgresSchedulePersistenceConfig;\n private logger: Logger | undefined;\n\n constructor(config: PostgresSchedulePersistenceConfig) {\n this.schema = config.schema ?? 'public';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {\n throw new Error(\n `Invalid schema name \"${this.schema}\". Schema must start with a letter or underscore, ` +\n `contain only alphanumeric characters and underscores, and be at most 63 characters.`\n );\n }\n this.tableName = config.tableName ?? 'workflow_schedules';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.tableName)) {\n throw new Error(\n `Invalid table name \"${this.tableName}\". Table name must start with a letter or underscore, ` +\n `contain only alphanumeric characters and underscores, and be at most 63 characters.`\n );\n }\n this.autoMigrate = config.autoMigrate !== false;\n this.config = config;\n this.logger = config.logger;\n }\n\n /**\n * Get a schema-scoped query builder.\n * All queries MUST use this instead of this.db directly to respect config.schema.\n */\n private get qb() {\n return this.db.withSchema(this.schema);\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n throw new Error(\n 'PostgresSchedulePersistence is not initialized. Call initialize() before using the adapter.'\n );\n }\n }\n\n /**\n * Initialize the persistence layer.\n * Creates the schedules table if autoMigrate is enabled.\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n const deps = await loadPostgresDeps();\n Kysely = deps.Kysely;\n PostgresDialect = deps.PostgresDialect;\n sql = deps.sql;\n pgModule = deps.pgModule;\n\n if (this.config.pool) {\n this.pool = this.config.pool;\n this.ownsPool = false;\n } else if (this.config.connectionString) {\n this.pool = new pgModule.Pool({ connectionString: this.config.connectionString });\n this.ownsPool = true;\n } else if (this.config.poolConfig) {\n this.pool = new pgModule.Pool(this.config.poolConfig);\n this.ownsPool = true;\n } else {\n throw new Error(\n 'PostgresSchedulePersistenceConfig must include either pool, connectionString, or poolConfig'\n );\n }\n\n this.db = new Kysely({\n dialect: new PostgresDialect({\n pool: this.pool,\n }),\n });\n\n if (this.autoMigrate) {\n await this.createTables();\n }\n\n this.initialized = true;\n }\n\n /**\n * Close the database connection.\n * Only closes the pool if it was created by this adapter.\n */\n async close(): Promise<void> {\n await this.db.destroy();\n if (this.ownsPool) {\n await this.pool.end();\n }\n }\n\n // ============================================================================\n // Database Initialization\n // ============================================================================\n\n private async createTables(): Promise<void> {\n const fullTableName = this.schema === 'public'\n ? this.tableName\n : `${this.schema}.${this.tableName}`;\n\n // Create schema if not public\n if (this.schema !== 'public') {\n await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(this.db);\n }\n\n // Create schedules table.\n // Schema layout:\n // id — unique schedule identifier (TEXT PK)\n // workflow_kind — target workflow type to spawn\n // trigger_type — constrained to 'cron' | 'workflow_completed' | 'manual'\n // cron_expression / timezone — cron trigger configuration\n // trigger_on_workflow_kind — completion trigger source kind\n // trigger_on_status — JSONB array of RunStatus values for completion triggers\n // input_json / metadata_json — JSONB payloads for spawned runs\n // enabled — whether this schedule is active\n // last_run_at / last_run_id — most recent execution tracking\n // next_run_at — next scheduled execution time\n // created_at / updated_at — audit timestamps (TIMESTAMPTZ)\n // No migration strategy is currently implemented; table is created idempotently.\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql.table(fullTableName)} (\n id TEXT PRIMARY KEY,\n workflow_kind TEXT NOT NULL,\n trigger_type TEXT NOT NULL,\n cron_expression TEXT,\n timezone TEXT DEFAULT 'UTC',\n trigger_on_workflow_kind TEXT,\n trigger_on_status JSONB,\n input_json JSONB NOT NULL DEFAULT '{}',\n metadata_json JSONB NOT NULL DEFAULT '{}',\n enabled BOOLEAN NOT NULL DEFAULT true,\n last_run_at TIMESTAMPTZ,\n last_run_id TEXT,\n next_run_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n CONSTRAINT ${sql.ref(`${this.tableName}_trigger_type_check`)} CHECK (\n trigger_type IN ('cron', 'workflow_completed', 'manual')\n )\n )\n `.execute(this.db);\n\n // Create indexes\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_workflow_kind`)}\n ON ${sql.table(fullTableName)} (workflow_kind)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_enabled`)}\n ON ${sql.table(fullTableName)} (enabled)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_trigger_type`)}\n ON ${sql.table(fullTableName)} (trigger_type)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_next_run`)}\n ON ${sql.table(fullTableName)} (next_run_at)\n WHERE enabled = true\n `.execute(this.db);\n }\n\n // ============================================================================\n // SchedulePersistence Interface Implementation\n // ============================================================================\n\n async loadSchedules(): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n async saveSchedule(schedule: WorkflowSchedule): Promise<void> {\n this.ensureInitialized();\n await this.qb\n .insertInto(this.tableName as any)\n .values({\n id: schedule.id,\n workflow_kind: schedule.workflowKind,\n trigger_type: schedule.triggerType,\n cron_expression: schedule.cronExpression ?? null,\n timezone: schedule.timezone ?? null,\n trigger_on_workflow_kind: schedule.triggerOnWorkflowKind ?? null,\n trigger_on_status: schedule.triggerOnStatus ? JSON.stringify(schedule.triggerOnStatus) : null,\n input_json: schedule.input ? JSON.stringify(schedule.input) : null,\n metadata_json: schedule.metadata ? JSON.stringify(schedule.metadata) : null,\n enabled: schedule.enabled,\n last_run_at: schedule.lastRunAt ?? null,\n last_run_id: schedule.lastRunId ?? null,\n next_run_at: schedule.nextRunAt ?? null,\n created_at: new Date(),\n updated_at: new Date(),\n })\n .execute();\n }\n\n async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {\n this.ensureInitialized();\n // Partial update pattern: fetch the existing row, merge with incoming updates,\n // then build a column-level update object. Each field is only included if it was\n // explicitly provided in `updates` (or affected by the merge), so unchanged\n // columns are left untouched in the database.\n const existing = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('id', '=', scheduleId)\n .executeTakeFirst() as WorkflowSchedulesTable | undefined;\n\n if (!existing) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n const merged = {\n ...this.rowToSchedule(existing),\n ...updates,\n };\n\n // Field mapping: { domainKey, dbKey, serialize? }\n // Each entry maps a WorkflowSchedule field to its DB column, with optional serialization.\n const fieldMappings: Array<{\n domainKey: keyof WorkflowSchedule;\n dbKey: keyof WorkflowSchedulesTable;\n serialize?: (value: any) => any;\n }> = [\n { domainKey: 'workflowKind', dbKey: 'workflow_kind' },\n { domainKey: 'triggerType', dbKey: 'trigger_type' },\n { domainKey: 'cronExpression', dbKey: 'cron_expression' },\n { domainKey: 'timezone', dbKey: 'timezone' },\n { domainKey: 'triggerOnWorkflowKind', dbKey: 'trigger_on_workflow_kind' },\n { domainKey: 'triggerOnStatus', dbKey: 'trigger_on_status', serialize: v => v ? JSON.stringify(v) : null },\n { domainKey: 'input', dbKey: 'input_json', serialize: v => v ? JSON.stringify(v) : null },\n { domainKey: 'metadata', dbKey: 'metadata_json', serialize: v => v ? JSON.stringify(v) : null },\n { domainKey: 'enabled', dbKey: 'enabled' },\n { domainKey: 'lastRunAt', dbKey: 'last_run_at' },\n { domainKey: 'lastRunId', dbKey: 'last_run_id' },\n { domainKey: 'nextRunAt', dbKey: 'next_run_at' },\n ];\n\n const updateData: Partial<WorkflowSchedulesTable> = {\n updated_at: new Date(),\n };\n\n for (const { domainKey, dbKey, serialize } of fieldMappings) {\n if (updates[domainKey] !== undefined || merged[domainKey] !== undefined) {\n const value = serialize\n ? serialize(merged[domainKey])\n : (merged[domainKey] ?? null);\n (updateData as any)[dbKey] = value;\n }\n }\n\n await this.qb\n .updateTable(this.tableName as any)\n .set(updateData)\n .where('id', '=', scheduleId)\n .execute();\n }\n\n async deleteSchedule(scheduleId: string): Promise<void> {\n this.ensureInitialized();\n await this.qb\n .deleteFrom(this.tableName as any)\n .where('id', '=', scheduleId)\n .execute();\n }\n\n // ============================================================================\n // Additional Methods\n // ============================================================================\n\n /**\n * Get a schedule by ID.\n */\n async getSchedule(scheduleId: string): Promise<WorkflowSchedule | null> {\n this.ensureInitialized();\n const row = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('id', '=', scheduleId)\n .executeTakeFirst() as WorkflowSchedulesTable | undefined;\n\n return row ? this.rowToSchedule(row) : null;\n }\n\n /**\n * Get all enabled schedules that are due to run.\n */\n async getDueSchedules(): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const now = new Date();\n\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('enabled', '=', true)\n .where('trigger_type', '=', 'cron')\n .where('next_run_at', '<=', now)\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n /**\n * Get schedules by workflow kind.\n */\n async getSchedulesByWorkflowKind(workflowKind: string): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('workflow_kind', '=', workflowKind)\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n /**\n * Get workflow completion triggers for a specific workflow kind.\n */\n async getCompletionTriggers(triggerOnWorkflowKind: string): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('enabled', '=', true)\n .where('trigger_type', '=', 'workflow_completed')\n .where('trigger_on_workflow_kind', '=', triggerOnWorkflowKind)\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n // ============================================================================\n // Helper Methods\n // ============================================================================\n\n private safeParseOptionalField(value: unknown, rowId: string, column: string): unknown {\n if (!value) return undefined;\n if (typeof value === 'string') {\n return sharedSafeJsonParse(value, undefined, {\n component: 'PostgresSchedulePersistence',\n rowId,\n column,\n logger: this.logger,\n });\n }\n return value;\n }\n\n private rowToSchedule(row: WorkflowSchedulesTable): WorkflowSchedule {\n return {\n id: row.id,\n workflowKind: row.workflow_kind,\n triggerType: row.trigger_type as WorkflowSchedule['triggerType'],\n cronExpression: row.cron_expression ?? undefined,\n timezone: row.timezone ?? undefined,\n triggerOnWorkflowKind: row.trigger_on_workflow_kind ?? undefined,\n triggerOnStatus: this.safeParseOptionalField(row.trigger_on_status, row.id, 'trigger_on_status') as RunStatus[] | undefined,\n input: this.safeParseOptionalField(row.input_json, row.id, 'input_json') as Record<string, unknown> | undefined,\n metadata: this.safeParseOptionalField(row.metadata_json, row.id, 'metadata_json') as Record<string, unknown> | undefined,\n enabled: row.enabled,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : undefined,\n lastRunId: row.last_run_id ?? undefined,\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : undefined,\n };\n }\n}\n\n// Convenience alias for shorter naming\nexport type { PostgresSchedulePersistenceConfig as PostgresSchedulePersistenceOptions };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/scheduler/cron.ts","../src/scheduler/sqlite-persistence.ts","../src/scheduler/postgres-persistence.ts"],"sourcesContent":["/**\n * CronScheduler - Scheduler implementation using cron expressions.\n *\n * Provides time-based and workflow-completion-based triggers for workflows.\n */\n\nimport { CronExpressionParser } from 'cron-parser';\nimport type { WorkflowKind, RunStatus, Logger } from '../core/types';\nimport type { WorkflowEngine } from '../core/engine';\nimport type { Scheduler, WorkflowSchedule, TriggerType } from './types';\nimport type { WorkflowEvent } from '../events/types';\nimport { generateId } from '../utils/id';\nimport { ConsoleLogger } from '../utils/logger';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for the CronScheduler.\n */\nexport interface CronSchedulerConfig {\n /** The workflow engine instance */\n engine: WorkflowEngine;\n\n /** Logger instance */\n logger?: Logger;\n\n /** Poll interval for checking schedules (ms, default: 1000) */\n pollInterval?: number;\n\n /** Optional persistence adapter for schedules */\n persistence?: SchedulePersistence;\n}\n\n/**\n * Interface for schedule persistence.\n * Implement this to persist schedules to a database.\n */\nexport interface SchedulePersistence {\n /** Load all schedules from storage */\n loadSchedules(): Promise<WorkflowSchedule[]>;\n\n /** Save a schedule */\n saveSchedule(schedule: WorkflowSchedule): Promise<void>;\n\n /** Update a schedule */\n updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void>;\n\n /** Delete a schedule */\n deleteSchedule(scheduleId: string): Promise<void>;\n}\n\n// ============================================================================\n// CronScheduler Class\n// ============================================================================\n\n/**\n * Scheduler implementation that supports cron expressions and workflow completion triggers.\n *\n * @example\n * ```typescript\n * const scheduler = new CronScheduler({\n * engine,\n * pollInterval: 1000,\n * });\n *\n * // Add a cron schedule (every day at midnight)\n * await scheduler.addSchedule({\n * workflowKind: 'cleanup.daily',\n * triggerType: 'cron',\n * cronExpression: '0 0 * * *',\n * enabled: true,\n * });\n *\n * // Add a workflow completion trigger\n * await scheduler.addSchedule({\n * workflowKind: 'notification.send',\n * triggerType: 'workflow_completed',\n * triggerOnWorkflowKind: 'order.process',\n * triggerOnStatus: ['succeeded'],\n * enabled: true,\n * });\n *\n * await scheduler.start();\n * ```\n */\nexport class CronScheduler implements Scheduler {\n private engine: WorkflowEngine;\n private logger: Logger;\n private pollInterval: number;\n private persistence?: SchedulePersistence;\n\n private schedules = new Map<string, WorkflowSchedule>();\n private running = false;\n private pollTimer: ReturnType<typeof setInterval> | null = null;\n private eventUnsubscribe: (() => void) | null = null;\n\n constructor(config: CronSchedulerConfig) {\n this.engine = config.engine;\n this.logger = config.logger ?? new ConsoleLogger();\n this.pollInterval = config.pollInterval ?? 1000;\n this.persistence = config.persistence;\n }\n\n // ============================================================================\n // Scheduler Interface Implementation\n // ============================================================================\n\n /**\n * Start the scheduler.\n * Begins polling for cron schedules and subscribes to workflow completion events.\n */\n async start(): Promise<void> {\n if (this.running) {\n this.logger.warn('Scheduler is already running');\n return;\n }\n\n // Clean up any leftover subscriptions from a previous start/stop cycle\n if (this.eventUnsubscribe) {\n this.eventUnsubscribe();\n this.eventUnsubscribe = null;\n }\n if (this.pollTimer) {\n clearInterval(this.pollTimer);\n this.pollTimer = null;\n }\n\n this.logger.info('Starting scheduler...');\n\n // Load schedules from persistence if available\n if (this.persistence) {\n const loaded = await this.persistence.loadSchedules();\n for (const schedule of loaded) {\n this.schedules.set(schedule.id, schedule);\n }\n this.logger.info(`Loaded ${loaded.length} schedules from persistence`);\n }\n\n // Calculate next run times for cron schedules.\n // Wrap each schedule in try-catch so one invalid persisted cron expression\n // doesn't prevent the rest from loading.\n for (const schedule of this.schedules.values()) {\n if (schedule.triggerType === 'cron' && schedule.cronExpression) {\n try {\n CronExpressionParser.parse(schedule.cronExpression, {\n tz: schedule.timezone,\n });\n this.updateNextRunTime(schedule);\n } catch (error) {\n this.logger.error(\n `Invalid cron expression \"${schedule.cronExpression}\" for schedule ${schedule.id}, disabling schedule:`,\n error\n );\n schedule.enabled = false;\n schedule.nextRunAt = undefined;\n }\n }\n }\n\n // Start polling for cron schedules\n this.pollTimer = setInterval(() => this.checkSchedules(), this.pollInterval);\n\n // Subscribe to workflow completion events\n this.eventUnsubscribe = this.engine.subscribeToAll((event) => {\n this.handleWorkflowEvent(event);\n });\n\n this.running = true;\n this.logger.info('Scheduler started');\n }\n\n /**\n * Stop the scheduler.\n * Stops polling and unsubscribes from events.\n */\n async stop(): Promise<void> {\n if (!this.running) {\n return;\n }\n\n this.logger.info('Stopping scheduler...');\n\n if (this.pollTimer) {\n clearInterval(this.pollTimer);\n this.pollTimer = null;\n }\n\n if (this.eventUnsubscribe) {\n this.eventUnsubscribe();\n this.eventUnsubscribe = null;\n }\n\n this.running = false;\n this.logger.info('Scheduler stopped');\n }\n\n /**\n * Add a new schedule.\n */\n async addSchedule(scheduleData: Omit<WorkflowSchedule, 'id'>): Promise<WorkflowSchedule> {\n const schedule: WorkflowSchedule = {\n ...scheduleData,\n id: generateId(),\n };\n\n // Validate cron expression if provided\n if (schedule.triggerType === 'cron' && schedule.cronExpression) {\n try {\n CronExpressionParser.parse(schedule.cronExpression, {\n tz: schedule.timezone,\n });\n } catch (error) {\n throw new Error(`Invalid cron expression: ${schedule.cronExpression}`);\n }\n\n // Calculate next run time\n this.updateNextRunTime(schedule);\n }\n\n // Validate workflow completion trigger\n if (schedule.triggerType === 'workflow_completed') {\n if (!schedule.triggerOnWorkflowKind) {\n throw new Error('triggerOnWorkflowKind is required for workflow_completed triggers');\n }\n }\n\n this.schedules.set(schedule.id, schedule);\n\n // Persist if available\n if (this.persistence) {\n await this.persistence.saveSchedule(schedule);\n }\n\n this.logger.info(`Added schedule: ${schedule.id} (${schedule.workflowKind})`);\n\n return schedule;\n }\n\n /**\n * Remove a schedule.\n */\n async removeSchedule(scheduleId: string): Promise<void> {\n if (!this.schedules.has(scheduleId)) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n this.schedules.delete(scheduleId);\n\n if (this.persistence) {\n await this.persistence.deleteSchedule(scheduleId);\n }\n\n this.logger.info(`Removed schedule: ${scheduleId}`);\n }\n\n /**\n * Update a schedule.\n */\n async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {\n const schedule = this.schedules.get(scheduleId);\n if (!schedule) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n // Don't allow changing the ID\n delete updates.id;\n\n // Update the schedule\n Object.assign(schedule, updates);\n\n // Revalidate and update next run time if cron expression changed\n if (updates.cronExpression !== undefined || updates.timezone !== undefined) {\n if (schedule.cronExpression) {\n try {\n CronExpressionParser.parse(schedule.cronExpression, {\n tz: schedule.timezone,\n });\n this.updateNextRunTime(schedule);\n } catch (error) {\n throw new Error(`Invalid cron expression: ${schedule.cronExpression}`);\n }\n }\n }\n\n if (this.persistence) {\n await this.persistence.updateSchedule(scheduleId, updates);\n }\n\n this.logger.debug(`Updated schedule: ${scheduleId}`);\n }\n\n /**\n * Get all schedules.\n */\n async getSchedules(): Promise<WorkflowSchedule[]> {\n return Array.from(this.schedules.values());\n }\n\n /**\n * Get a schedule by ID.\n */\n getSchedule(scheduleId: string): WorkflowSchedule | undefined {\n return this.schedules.get(scheduleId);\n }\n\n /**\n * Manually trigger a scheduled workflow.\n */\n async triggerNow(scheduleId: string): Promise<string> {\n const schedule = this.schedules.get(scheduleId);\n if (!schedule) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n return this.executeSchedule(schedule);\n }\n\n // ============================================================================\n // Internal Methods\n // ============================================================================\n\n /**\n * Check all cron schedules and execute those that are due.\n */\n private checkSchedules(): void {\n const now = new Date();\n\n for (const schedule of this.schedules.values()) {\n if (!schedule.enabled) continue;\n if (schedule.triggerType !== 'cron') continue;\n if (!schedule.nextRunAt) continue;\n\n if (schedule.nextRunAt <= now) {\n // Execute the schedule\n this.executeSchedule(schedule).catch((error) => {\n this.logger.error(`Failed to execute schedule ${schedule.id}:`, error);\n });\n\n // Update next run time\n this.updateNextRunTime(schedule);\n }\n }\n }\n\n /**\n * Handle workflow events for completion triggers.\n */\n private handleWorkflowEvent(event: WorkflowEvent): void {\n // Only handle run completion events\n // Note: orchestrator emits 'run.completed' for success, 'run.failed' for failure\n if (event.eventType !== 'run.completed' && event.eventType !== 'run.failed') {\n return;\n }\n\n const completedStatus: RunStatus = event.eventType === 'run.completed' ? 'succeeded' : 'failed';\n const completedKind = event.kind;\n\n // Find schedules triggered by this workflow completion\n for (const schedule of this.schedules.values()) {\n if (!schedule.enabled) continue;\n if (schedule.triggerType !== 'workflow_completed') continue;\n if (schedule.triggerOnWorkflowKind !== completedKind) continue;\n\n // Check if the status matches\n if (schedule.triggerOnStatus && !schedule.triggerOnStatus.includes(completedStatus)) {\n continue;\n }\n\n // Execute the schedule\n this.executeSchedule(schedule, {\n triggerRunId: event.runId,\n triggerStatus: completedStatus,\n }).catch((error) => {\n this.logger.error(`Failed to execute schedule ${schedule.id}:`, error);\n });\n }\n }\n\n /**\n * Execute a schedule by starting the workflow.\n */\n private async executeSchedule(\n schedule: WorkflowSchedule,\n triggerContext?: { triggerRunId?: string; triggerStatus?: RunStatus }\n ): Promise<string> {\n this.logger.info(`Executing schedule: ${schedule.id} (${schedule.workflowKind})`);\n\n // Build metadata with trigger info\n const metadata = {\n ...schedule.metadata,\n scheduleId: schedule.id,\n triggerType: schedule.triggerType,\n ...(triggerContext ?? {}),\n };\n\n // Start the workflow\n const runId = await this.engine.startRun({\n kind: schedule.workflowKind,\n input: schedule.input,\n metadata,\n });\n\n // Update schedule state\n schedule.lastRunAt = new Date();\n schedule.lastRunId = runId;\n\n if (this.persistence) {\n await this.persistence.updateSchedule(schedule.id, {\n lastRunAt: schedule.lastRunAt,\n lastRunId: schedule.lastRunId,\n nextRunAt: schedule.nextRunAt,\n });\n }\n\n return runId;\n }\n\n /**\n * Update the next run time for a cron schedule.\n */\n private updateNextRunTime(schedule: WorkflowSchedule): void {\n if (!schedule.cronExpression) return;\n\n try {\n const interval = CronExpressionParser.parse(schedule.cronExpression, {\n currentDate: new Date(),\n tz: schedule.timezone,\n });\n\n schedule.nextRunAt = interval.next().toDate();\n } catch (error) {\n this.logger.error(`Failed to parse cron expression for schedule ${schedule.id}:`, error);\n schedule.nextRunAt = undefined;\n }\n }\n}\n","/**\n * SQLite persistence adapter for schedules.\n *\n * Stores workflow schedules in a SQLite database table.\n */\n\nimport type { Database, Statement } from 'better-sqlite3';\nimport type { WorkflowSchedule } from './types';\nimport type { SchedulePersistence } from './cron';\nimport type { Logger, RunStatus } from '../core/types';\nimport { generateId } from '../utils/id';\nimport { safeJsonParse as sharedSafeJsonParse } from '../utils/safe-json';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for SQLiteSchedulePersistence.\n */\nexport interface SQLiteSchedulePersistenceConfig {\n /** SQLite database instance */\n db: Database;\n\n /** Table name for schedules (default: workflow_schedules) */\n tableName?: string;\n\n /**\n * Optional structured logger used to surface JSON corruption events.\n * Without one, parse failures still increment the corruption counter\n * exposed via `getJsonParseCorruptionCount()` but produce no log output.\n */\n logger?: Logger;\n}\n\n// ============================================================================\n// SQLiteSchedulePersistence Class\n// ============================================================================\n\n/**\n * SQLite-based persistence for workflow schedules.\n *\n * @example\n * ```typescript\n * import Database from 'better-sqlite3';\n *\n * const db = new Database('workflow.db');\n * const persistence = new SQLiteSchedulePersistence({ db });\n *\n * const scheduler = new CronScheduler({\n * engine,\n * persistence,\n * });\n * ```\n */\nexport class SQLiteSchedulePersistence implements SchedulePersistence {\n private db: Database;\n private tableName: string;\n private logger: Logger | undefined;\n private stmts: {\n insert: Statement;\n update: Statement;\n delete: Statement;\n getAll: Statement;\n getById: Statement;\n } | null = null;\n\n constructor(config: SQLiteSchedulePersistenceConfig) {\n this.db = config.db;\n this.tableName = config.tableName ?? 'workflow_schedules';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(this.tableName)) {\n throw new Error(`Invalid table name: ${this.tableName}`);\n }\n this.logger = config.logger;\n this.initializeDatabase();\n }\n\n // ============================================================================\n // Database Initialization\n // ============================================================================\n\n private initializeDatabase(): void {\n // Create schedules table.\n // Schema layout:\n // id — unique schedule identifier (TEXT PK)\n // workflow_kind — target workflow type to spawn\n // trigger_type — 'cron', 'workflow_completed', or 'manual'\n // cron_expression — cron expression for time-based triggers (nullable)\n // timezone — IANA timezone for cron evaluation (nullable)\n // trigger_on_* — fields for workflow-completion triggers\n // input / metadata — JSON-serialized payloads for spawned runs\n // enabled — whether this schedule is active (0/1)\n // last_run_at/id — most recent execution tracking\n // next_run_at — next scheduled execution time\n // created_at/updated_at — audit timestamps\n // No migration strategy is currently implemented; table is created idempotently.\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n id TEXT PRIMARY KEY,\n workflow_kind TEXT NOT NULL,\n trigger_type TEXT NOT NULL,\n cron_expression TEXT,\n timezone TEXT,\n trigger_on_workflow_kind TEXT,\n trigger_on_status TEXT,\n input TEXT,\n metadata TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_run_at TEXT,\n last_run_id TEXT,\n next_run_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `);\n\n // Create indexes\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_workflow_kind\n ON ${this.tableName}(workflow_kind);\n\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_enabled\n ON ${this.tableName}(enabled);\n\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_trigger_type\n ON ${this.tableName}(trigger_type);\n `);\n\n // Prepare statements\n this.stmts = {\n insert: this.db.prepare(`\n INSERT INTO ${this.tableName} (\n id, workflow_kind, trigger_type, cron_expression, timezone,\n trigger_on_workflow_kind, trigger_on_status, input, metadata,\n enabled, last_run_at, last_run_id, next_run_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `),\n update: this.db.prepare(`\n UPDATE ${this.tableName}\n SET workflow_kind = COALESCE(?, workflow_kind),\n trigger_type = COALESCE(?, trigger_type),\n cron_expression = ?,\n timezone = ?,\n trigger_on_workflow_kind = ?,\n trigger_on_status = ?,\n input = ?,\n metadata = ?,\n enabled = COALESCE(?, enabled),\n last_run_at = ?,\n last_run_id = ?,\n next_run_at = ?,\n updated_at = datetime('now')\n WHERE id = ?\n `),\n delete: this.db.prepare(`\n DELETE FROM ${this.tableName} WHERE id = ?\n `),\n getAll: this.db.prepare(`\n SELECT * FROM ${this.tableName}\n `),\n getById: this.db.prepare(`\n SELECT * FROM ${this.tableName} WHERE id = ?\n `),\n };\n }\n\n // ============================================================================\n // SchedulePersistence Interface Implementation\n // ============================================================================\n\n async loadSchedules(): Promise<WorkflowSchedule[]> {\n const rows = this.stmts!.getAll.all() as ScheduleRow[];\n return rows.map(row => this.rowToSchedule(row));\n }\n\n async saveSchedule(schedule: WorkflowSchedule): Promise<void> {\n this.stmts!.insert.run(\n schedule.id,\n schedule.workflowKind,\n schedule.triggerType,\n schedule.cronExpression ?? null,\n schedule.timezone ?? null,\n schedule.triggerOnWorkflowKind ?? null,\n schedule.triggerOnStatus ? JSON.stringify(schedule.triggerOnStatus) : null,\n schedule.input ? JSON.stringify(schedule.input) : null,\n schedule.metadata ? JSON.stringify(schedule.metadata) : null,\n schedule.enabled ? 1 : 0,\n schedule.lastRunAt?.toISOString() ?? null,\n schedule.lastRunId ?? null,\n schedule.nextRunAt?.toISOString() ?? null\n );\n }\n\n async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {\n // Get existing schedule to merge with updates\n const existing = this.stmts!.getById.get(scheduleId) as ScheduleRow | undefined;\n if (!existing) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n const merged = {\n ...this.rowToSchedule(existing),\n ...updates,\n };\n\n this.stmts!.update.run(\n updates.workflowKind ?? null,\n updates.triggerType ?? null,\n merged.cronExpression ?? null,\n merged.timezone ?? null,\n merged.triggerOnWorkflowKind ?? null,\n merged.triggerOnStatus ? JSON.stringify(merged.triggerOnStatus) : null,\n merged.input ? JSON.stringify(merged.input) : null,\n merged.metadata ? JSON.stringify(merged.metadata) : null,\n updates.enabled !== undefined ? (updates.enabled ? 1 : 0) : null,\n merged.lastRunAt?.toISOString() ?? null,\n merged.lastRunId ?? null,\n merged.nextRunAt?.toISOString() ?? null,\n scheduleId\n );\n }\n\n async deleteSchedule(scheduleId: string): Promise<void> {\n this.stmts!.delete.run(scheduleId);\n }\n\n // ============================================================================\n // Helper Methods\n // ============================================================================\n\n private safeJsonParse(\n json: string,\n fallback: unknown,\n rowId: string,\n column: string,\n ): unknown {\n return sharedSafeJsonParse(json, fallback, {\n component: 'SQLiteSchedulePersistence',\n rowId,\n column,\n logger: this.logger,\n });\n }\n\n private rowToSchedule(row: ScheduleRow): WorkflowSchedule {\n return {\n id: row.id,\n workflowKind: row.workflow_kind,\n triggerType: row.trigger_type as WorkflowSchedule['triggerType'],\n cronExpression: row.cron_expression ?? undefined,\n timezone: row.timezone ?? undefined,\n triggerOnWorkflowKind: row.trigger_on_workflow_kind ?? undefined,\n triggerOnStatus: row.trigger_on_status\n ? this.safeJsonParse(row.trigger_on_status, undefined, row.id, 'trigger_on_status') as RunStatus[] | undefined\n : undefined,\n input: row.input ? this.safeJsonParse(row.input, undefined, row.id, 'input') as Record<string, unknown> | undefined : undefined,\n metadata: row.metadata ? this.safeJsonParse(row.metadata, undefined, row.id, 'metadata') as Record<string, unknown> | undefined : undefined,\n enabled: row.enabled === 1,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : undefined,\n lastRunId: row.last_run_id ?? undefined,\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : undefined,\n };\n }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface ScheduleRow {\n id: string;\n workflow_kind: string;\n trigger_type: string;\n cron_expression: string | null;\n timezone: string | null;\n trigger_on_workflow_kind: string | null;\n trigger_on_status: string | null;\n input: string | null;\n metadata: string | null;\n enabled: number;\n last_run_at: string | null;\n last_run_id: string | null;\n next_run_at: string | null;\n created_at: string;\n updated_at: string;\n}\n","/**\n * PostgreSQL persistence adapter for schedules.\n *\n * Stores workflow schedules in a PostgreSQL database table using Kysely.\n */\n\nimport type { Kysely as KyselyType } from 'kysely';\nimport type { Pool, PoolConfig } from 'pg';\nimport { loadPostgresDeps } from '../utils/postgres-deps.js';\n\n// Lazy-loaded dependencies - populated by loadPostgresDeps() during initialize()\nlet Kysely: any;\nlet PostgresDialect: any;\nlet sql: any;\nlet pgModule: any;\nimport type { WorkflowSchedule } from './types.js';\nimport type { SchedulePersistence } from './cron.js';\nimport type { Logger, RunStatus } from '../core/types.js';\nimport { safeJsonParse as sharedSafeJsonParse } from '../utils/safe-json.js';\n\n// ============================================================================\n// Database Types (Kysely schema)\n// ============================================================================\n\n/** Kysely row type for the workflow_schedules table. */\ninterface WorkflowSchedulesTable {\n id: string;\n /** Workflow kind this schedule targets. */\n workflow_kind: string;\n /** Trigger type: 'cron', 'workflow_completed', or 'manual'. */\n trigger_type: string;\n /** Cron expression for time-based triggers, null for non-cron schedules. */\n cron_expression: string | null;\n /** IANA timezone for cron evaluation, null defaults to UTC. */\n timezone: string | null;\n /** Workflow kind that triggers on completion, null for non-completion triggers. */\n trigger_on_workflow_kind: string | null;\n /** JSON-serialized array of RunStatus values that activate the trigger. */\n trigger_on_status: string | null;\n /** JSON-serialized input payload for spawned runs. */\n input_json: string | null;\n /** JSON-serialized arbitrary metadata for spawned runs. */\n metadata_json: string | null;\n /** Whether this schedule is active. */\n enabled: boolean;\n /** Timestamp of the most recent run spawned by this schedule. */\n last_run_at: Date | null;\n /** Run ID of the most recent run spawned by this schedule. */\n last_run_id: string | null;\n /** Next scheduled execution time. */\n next_run_at: Date | null;\n created_at: Date;\n updated_at: Date;\n}\n\ninterface SchedulesDatabase {\n workflow_schedules: WorkflowSchedulesTable;\n}\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for PostgresSchedulePersistence.\n */\nexport interface PostgresSchedulePersistenceConfig {\n /**\n * PostgreSQL connection string.\n * Example: \"postgresql://user:pass@localhost:5432/dbname\"\n */\n connectionString?: string;\n\n /**\n * Existing pg.Pool instance for connection sharing with application.\n * If provided, the adapter will not close this pool on close().\n */\n pool?: Pool;\n\n /**\n * Pool configuration options (if not providing pool or connectionString).\n */\n poolConfig?: PoolConfig;\n\n /**\n * Schema name for the schedules table.\n * @default 'public'\n */\n schema?: string;\n\n /**\n * Table name for schedules.\n * @default 'workflow_schedules'\n */\n tableName?: string;\n\n /**\n * Automatically create tables on initialization.\n * @default true\n */\n autoMigrate?: boolean;\n\n /**\n * Optional structured logger used to surface JSON corruption events.\n * Without one, parse failures still increment the corruption counter\n * exposed via `getJsonParseCorruptionCount()` but produce no log output.\n */\n logger?: Logger;\n}\n\n// ============================================================================\n// PostgresSchedulePersistence Class\n// ============================================================================\n\n/**\n * PostgreSQL-based persistence for workflow schedules.\n *\n * @example\n * ```typescript\n * const persistence = new PostgresSchedulePersistence({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * await persistence.initialize();\n *\n * const scheduler = new CronScheduler({\n * engine,\n * persistence,\n * });\n * ```\n *\n * @example Sharing connection pool\n * ```typescript\n * import pg from 'pg';\n *\n * const pool = new pg.Pool({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const persistence = new PostgresSchedulePersistence({ pool });\n * await persistence.initialize();\n * ```\n */\nexport class PostgresSchedulePersistence implements SchedulePersistence {\n private db!: KyselyType<SchedulesDatabase>;\n private pool!: Pool;\n private ownsPool = false;\n private schema: string;\n private tableName: string;\n private autoMigrate: boolean;\n private initialized = false;\n private config: PostgresSchedulePersistenceConfig;\n private logger: Logger | undefined;\n\n constructor(config: PostgresSchedulePersistenceConfig) {\n this.schema = config.schema ?? 'public';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {\n throw new Error(\n `Invalid schema name \"${this.schema}\". Schema must start with a letter or underscore, ` +\n `contain only alphanumeric characters and underscores, and be at most 63 characters.`\n );\n }\n this.tableName = config.tableName ?? 'workflow_schedules';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.tableName)) {\n throw new Error(\n `Invalid table name \"${this.tableName}\". Table name must start with a letter or underscore, ` +\n `contain only alphanumeric characters and underscores, and be at most 63 characters.`\n );\n }\n this.autoMigrate = config.autoMigrate !== false;\n this.config = config;\n this.logger = config.logger;\n }\n\n /**\n * Get a schema-scoped query builder.\n * All queries MUST use this instead of this.db directly to respect config.schema.\n */\n private get qb() {\n return this.db.withSchema(this.schema);\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n throw new Error(\n 'PostgresSchedulePersistence is not initialized. Call initialize() before using the adapter.'\n );\n }\n }\n\n /**\n * Initialize the persistence layer.\n * Creates the schedules table if autoMigrate is enabled.\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n const deps = await loadPostgresDeps();\n Kysely = deps.Kysely;\n PostgresDialect = deps.PostgresDialect;\n sql = deps.sql;\n pgModule = deps.pgModule;\n\n if (this.config.pool) {\n this.pool = this.config.pool;\n this.ownsPool = false;\n } else if (this.config.connectionString) {\n this.pool = new pgModule.Pool({ connectionString: this.config.connectionString });\n this.ownsPool = true;\n } else if (this.config.poolConfig) {\n this.pool = new pgModule.Pool(this.config.poolConfig);\n this.ownsPool = true;\n } else {\n throw new Error(\n 'PostgresSchedulePersistenceConfig must include either pool, connectionString, or poolConfig'\n );\n }\n\n this.db = new Kysely({\n dialect: new PostgresDialect({\n pool: this.pool,\n }),\n });\n\n if (this.autoMigrate) {\n await this.createTables();\n }\n\n this.initialized = true;\n }\n\n /**\n * Close the database connection.\n * Only closes the pool if it was created by this adapter.\n */\n async close(): Promise<void> {\n // destroy() ends the pool exactly once; never destroy a borrowed pool.\n if (this.db && this.ownsPool) await this.db.destroy();\n this.initialized = false;\n }\n\n // ============================================================================\n // Database Initialization\n // ============================================================================\n\n private async createTables(): Promise<void> {\n const fullTableName = this.schema === 'public'\n ? this.tableName\n : `${this.schema}.${this.tableName}`;\n\n // Create schema if not public\n if (this.schema !== 'public') {\n await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(this.db);\n }\n\n // Create schedules table.\n // Schema layout:\n // id — unique schedule identifier (TEXT PK)\n // workflow_kind — target workflow type to spawn\n // trigger_type — constrained to 'cron' | 'workflow_completed' | 'manual'\n // cron_expression / timezone — cron trigger configuration\n // trigger_on_workflow_kind — completion trigger source kind\n // trigger_on_status — JSONB array of RunStatus values for completion triggers\n // input_json / metadata_json — JSONB payloads for spawned runs\n // enabled — whether this schedule is active\n // last_run_at / last_run_id — most recent execution tracking\n // next_run_at — next scheduled execution time\n // created_at / updated_at — audit timestamps (TIMESTAMPTZ)\n // No migration strategy is currently implemented; table is created idempotently.\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql.table(fullTableName)} (\n id TEXT PRIMARY KEY,\n workflow_kind TEXT NOT NULL,\n trigger_type TEXT NOT NULL,\n cron_expression TEXT,\n timezone TEXT DEFAULT 'UTC',\n trigger_on_workflow_kind TEXT,\n trigger_on_status JSONB,\n input_json JSONB NOT NULL DEFAULT '{}',\n metadata_json JSONB NOT NULL DEFAULT '{}',\n enabled BOOLEAN NOT NULL DEFAULT true,\n last_run_at TIMESTAMPTZ,\n last_run_id TEXT,\n next_run_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n CONSTRAINT ${sql.ref(`${this.tableName}_trigger_type_check`)} CHECK (\n trigger_type IN ('cron', 'workflow_completed', 'manual')\n )\n )\n `.execute(this.db);\n\n // Create indexes\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_workflow_kind`)}\n ON ${sql.table(fullTableName)} (workflow_kind)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_enabled`)}\n ON ${sql.table(fullTableName)} (enabled)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_trigger_type`)}\n ON ${sql.table(fullTableName)} (trigger_type)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${this.tableName}_next_run`)}\n ON ${sql.table(fullTableName)} (next_run_at)\n WHERE enabled = true\n `.execute(this.db);\n }\n\n // ============================================================================\n // SchedulePersistence Interface Implementation\n // ============================================================================\n\n async loadSchedules(): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n async saveSchedule(schedule: WorkflowSchedule): Promise<void> {\n this.ensureInitialized();\n await this.qb\n .insertInto(this.tableName as any)\n .values({\n id: schedule.id,\n workflow_kind: schedule.workflowKind,\n trigger_type: schedule.triggerType,\n cron_expression: schedule.cronExpression ?? null,\n timezone: schedule.timezone ?? null,\n trigger_on_workflow_kind: schedule.triggerOnWorkflowKind ?? null,\n trigger_on_status: schedule.triggerOnStatus ? JSON.stringify(schedule.triggerOnStatus) : null,\n input_json: schedule.input ? JSON.stringify(schedule.input) : null,\n metadata_json: schedule.metadata ? JSON.stringify(schedule.metadata) : null,\n enabled: schedule.enabled,\n last_run_at: schedule.lastRunAt ?? null,\n last_run_id: schedule.lastRunId ?? null,\n next_run_at: schedule.nextRunAt ?? null,\n created_at: new Date(),\n updated_at: new Date(),\n })\n .execute();\n }\n\n async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {\n this.ensureInitialized();\n // Partial update pattern: fetch the existing row, merge with incoming updates,\n // then build a column-level update object. Each field is only included if it was\n // explicitly provided in `updates` (or affected by the merge), so unchanged\n // columns are left untouched in the database.\n const existing = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('id', '=', scheduleId)\n .executeTakeFirst() as WorkflowSchedulesTable | undefined;\n\n if (!existing) {\n throw new Error(`Schedule not found: ${scheduleId}`);\n }\n\n const merged = {\n ...this.rowToSchedule(existing),\n ...updates,\n };\n\n // Field mapping: { domainKey, dbKey, serialize? }\n // Each entry maps a WorkflowSchedule field to its DB column, with optional serialization.\n const fieldMappings: Array<{\n domainKey: keyof WorkflowSchedule;\n dbKey: keyof WorkflowSchedulesTable;\n serialize?: (value: any) => any;\n }> = [\n { domainKey: 'workflowKind', dbKey: 'workflow_kind' },\n { domainKey: 'triggerType', dbKey: 'trigger_type' },\n { domainKey: 'cronExpression', dbKey: 'cron_expression' },\n { domainKey: 'timezone', dbKey: 'timezone' },\n { domainKey: 'triggerOnWorkflowKind', dbKey: 'trigger_on_workflow_kind' },\n { domainKey: 'triggerOnStatus', dbKey: 'trigger_on_status', serialize: v => v ? JSON.stringify(v) : null },\n { domainKey: 'input', dbKey: 'input_json', serialize: v => v ? JSON.stringify(v) : null },\n { domainKey: 'metadata', dbKey: 'metadata_json', serialize: v => v ? JSON.stringify(v) : null },\n { domainKey: 'enabled', dbKey: 'enabled' },\n { domainKey: 'lastRunAt', dbKey: 'last_run_at' },\n { domainKey: 'lastRunId', dbKey: 'last_run_id' },\n { domainKey: 'nextRunAt', dbKey: 'next_run_at' },\n ];\n\n const updateData: Partial<WorkflowSchedulesTable> = {\n updated_at: new Date(),\n };\n\n for (const { domainKey, dbKey, serialize } of fieldMappings) {\n if (updates[domainKey] !== undefined || merged[domainKey] !== undefined) {\n const value = serialize\n ? serialize(merged[domainKey])\n : (merged[domainKey] ?? null);\n (updateData as any)[dbKey] = value;\n }\n }\n\n await this.qb\n .updateTable(this.tableName as any)\n .set(updateData)\n .where('id', '=', scheduleId)\n .execute();\n }\n\n async deleteSchedule(scheduleId: string): Promise<void> {\n this.ensureInitialized();\n await this.qb\n .deleteFrom(this.tableName as any)\n .where('id', '=', scheduleId)\n .execute();\n }\n\n // ============================================================================\n // Additional Methods\n // ============================================================================\n\n /**\n * Get a schedule by ID.\n */\n async getSchedule(scheduleId: string): Promise<WorkflowSchedule | null> {\n this.ensureInitialized();\n const row = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('id', '=', scheduleId)\n .executeTakeFirst() as WorkflowSchedulesTable | undefined;\n\n return row ? this.rowToSchedule(row) : null;\n }\n\n /**\n * Get all enabled schedules that are due to run.\n */\n async getDueSchedules(): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const now = new Date();\n\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('enabled', '=', true)\n .where('trigger_type', '=', 'cron')\n .where('next_run_at', '<=', now)\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n /**\n * Get schedules by workflow kind.\n */\n async getSchedulesByWorkflowKind(workflowKind: string): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('workflow_kind', '=', workflowKind)\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n /**\n * Get workflow completion triggers for a specific workflow kind.\n */\n async getCompletionTriggers(triggerOnWorkflowKind: string): Promise<WorkflowSchedule[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom(this.tableName as any)\n .selectAll()\n .where('enabled', '=', true)\n .where('trigger_type', '=', 'workflow_completed')\n .where('trigger_on_workflow_kind', '=', triggerOnWorkflowKind)\n .execute() as WorkflowSchedulesTable[];\n\n return rows.map(row => this.rowToSchedule(row));\n }\n\n // ============================================================================\n // Helper Methods\n // ============================================================================\n\n private safeParseOptionalField(value: unknown, rowId: string, column: string): unknown {\n if (!value) return undefined;\n if (typeof value === 'string') {\n return sharedSafeJsonParse(value, undefined, {\n component: 'PostgresSchedulePersistence',\n rowId,\n column,\n logger: this.logger,\n });\n }\n return value;\n }\n\n private rowToSchedule(row: WorkflowSchedulesTable): WorkflowSchedule {\n return {\n id: row.id,\n workflowKind: row.workflow_kind,\n triggerType: row.trigger_type as WorkflowSchedule['triggerType'],\n cronExpression: row.cron_expression ?? undefined,\n timezone: row.timezone ?? undefined,\n triggerOnWorkflowKind: row.trigger_on_workflow_kind ?? undefined,\n triggerOnStatus: this.safeParseOptionalField(row.trigger_on_status, row.id, 'trigger_on_status') as RunStatus[] | undefined,\n input: this.safeParseOptionalField(row.input_json, row.id, 'input_json') as Record<string, unknown> | undefined,\n metadata: this.safeParseOptionalField(row.metadata_json, row.id, 'metadata_json') as Record<string, unknown> | undefined,\n enabled: row.enabled,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : undefined,\n lastRunId: row.last_run_id ?? undefined,\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : undefined,\n };\n }\n}\n\n// Convenience alias for shorter naming\nexport type { PostgresSchedulePersistenceConfig as PostgresSchedulePersistenceOptions };\n"],"mappings":"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","names":[]}
|
package/dist/events/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as EventTransport, W as WorkflowEvent, E as EventCallback, U as Unsubscribe, b as WorkflowEventType } from '../types-DZUdWmCc.js';
|
|
2
2
|
export { B as BuiltInEventType } from '../types-DZUdWmCc.js';
|
|
3
|
-
import {
|
|
3
|
+
import { Logger } from '../index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* In-memory event transport using Node.js EventEmitter.
|
|
@@ -19,6 +19,8 @@ declare class MemoryEventTransport implements EventTransport {
|
|
|
19
19
|
constructor();
|
|
20
20
|
/**
|
|
21
21
|
* Emit an event to all subscribers.
|
|
22
|
+
* Each channel is isolated so a failing listener on one channel
|
|
23
|
+
* cannot prevent delivery to the others.
|
|
22
24
|
*/
|
|
23
25
|
emit(event: WorkflowEvent): void;
|
|
24
26
|
/**
|
|
@@ -370,7 +372,8 @@ declare class WebhookEventTransport implements EventTransport {
|
|
|
370
372
|
* not private/reserved, and return the address + family so the caller can
|
|
371
373
|
* pin the connection to it.
|
|
372
374
|
*
|
|
373
|
-
*
|
|
375
|
+
* Raw-IP URLs are validated and pinned too; they must not fall back to a
|
|
376
|
+
* redirect-following HTTP client.
|
|
374
377
|
*
|
|
375
378
|
* Pinning the resolved IP closes the DNS rebinding TOCTOU window (audit M1):
|
|
376
379
|
* with the original implementation `fetch()` re-resolved the hostname,
|
|
@@ -396,4 +399,41 @@ declare class WebhookEventTransport implements EventTransport {
|
|
|
396
399
|
private enqueueRequest;
|
|
397
400
|
}
|
|
398
401
|
|
|
399
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Helpers for signing and verifying inbound / outbound webhook payloads.
|
|
404
|
+
*
|
|
405
|
+
* Both sides use HMAC-SHA256 with a `sha256=<lowercase-hex>` envelope —
|
|
406
|
+
* the same shape produced by `WebhookEventTransport.signPayload`, matching
|
|
407
|
+
* common third-party providers (Stripe, GitHub, etc.).
|
|
408
|
+
*
|
|
409
|
+
* Audit: 2026-04-27 finding L2 — without a shipped verifier, consumers
|
|
410
|
+
* receiving inbound webhooks tend to roll their own comparisons that are
|
|
411
|
+
* vulnerable to timing attacks.
|
|
412
|
+
*/
|
|
413
|
+
/**
|
|
414
|
+
* Compute the canonical `X-Webhook-Signature` value for a request body.
|
|
415
|
+
*
|
|
416
|
+
* Returns a string of the form `sha256=<lowercase-hex>`.
|
|
417
|
+
*/
|
|
418
|
+
declare function signWebhookPayload(body: string | Uint8Array, secret: string): string;
|
|
419
|
+
/**
|
|
420
|
+
* Constant-time verification of an inbound webhook signature.
|
|
421
|
+
*
|
|
422
|
+
* @param body The raw request body that was signed.
|
|
423
|
+
* @param signature The header value sent by the producer (e.g. `sha256=…`).
|
|
424
|
+
* @param secret The shared HMAC secret.
|
|
425
|
+
* @returns `true` only if the computed digest matches the supplied signature.
|
|
426
|
+
*
|
|
427
|
+
* Properties:
|
|
428
|
+
* - Accepts an optional `sha256=` prefix on the signature, but does not
|
|
429
|
+
* require it (mirrors common third-party header conventions).
|
|
430
|
+
* - Uses `timingSafeEqual` so an attacker cannot probe digit-by-digit.
|
|
431
|
+
* - Returns `false` for missing/empty values, non-hex input, and length
|
|
432
|
+
* mismatches without throwing.
|
|
433
|
+
* - Returns `false` if the secret is empty — an empty secret is almost
|
|
434
|
+
* always a misconfiguration we do not want to silently accept.
|
|
435
|
+
*/
|
|
436
|
+
declare function verifyWebhookSignature(body: string | Uint8Array, signature: string | undefined | null, secret: string): boolean;
|
|
437
|
+
|
|
438
|
+
export { EventCallback, EventTransport, MemoryEventTransport, SocketIOEventTransport, Unsubscribe, WebhookEventTransport, WorkflowEvent, WorkflowEventType, signWebhookPayload, verifyWebhookSignature };
|
|
439
|
+
export type { SocketIOAuthorizeFn, SocketIOEventTransportConfig, SocketIOServer, SocketIOSocket, WebhookEndpoint, WebhookEventTransportConfig, WebhookPayload };
|
package/dist/events/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MemoryEventTransport,
|
|
3
3
|
SocketIOEventTransport,
|
|
4
|
-
WebhookEventTransport
|
|
5
|
-
|
|
4
|
+
WebhookEventTransport,
|
|
5
|
+
signWebhookPayload,
|
|
6
|
+
verifyWebhookSignature
|
|
7
|
+
} from "../chunk-T6GAZBTQ.js";
|
|
6
8
|
export {
|
|
7
9
|
MemoryEventTransport,
|
|
8
10
|
SocketIOEventTransport,
|
|
9
|
-
WebhookEventTransport
|
|
11
|
+
WebhookEventTransport,
|
|
12
|
+
signWebhookPayload,
|
|
13
|
+
verifyWebhookSignature
|
|
10
14
|
};
|
|
11
15
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { L as Logger, g as WorkflowDefinition, W as WorkflowKind, R as RunStatus } from './types-K5Gjk3H_.js';
|
|
2
|
-
import { i as StorageAdapter, j as WorkflowRunRecord } from './types-
|
|
2
|
+
import { i as StorageAdapter, j as WorkflowRunRecord } from './types-BfWe5yP6.js';
|
|
3
3
|
import { a as EventTransport, E as EventCallback, U as Unsubscribe } from './types-DZUdWmCc.js';
|
|
4
4
|
import { Database } from 'better-sqlite3';
|
|
5
5
|
import { Pool, PoolConfig } from 'pg';
|
|
@@ -32,6 +32,11 @@ interface WorkflowEngineConfig {
|
|
|
32
32
|
defaultTimeout?: number;
|
|
33
33
|
/** Maximum concurrent workflows */
|
|
34
34
|
maxConcurrency?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum time in ms that shutdown waits for in-flight runs, hooks and
|
|
37
|
+
* admission I/O before closing adapters anyway (default: 30000).
|
|
38
|
+
*/
|
|
39
|
+
shutdownTimeout?: number;
|
|
35
40
|
};
|
|
36
41
|
}
|
|
37
42
|
/**
|
|
@@ -136,6 +141,7 @@ declare class WorkflowEngine {
|
|
|
136
141
|
* @throws WorkflowNotFoundError if the workflow kind is not registered
|
|
137
142
|
*/
|
|
138
143
|
startRun<TInput = Record<string, unknown>>(options: StartRunOptions<TInput>): Promise<string>;
|
|
144
|
+
private createAndStartRun;
|
|
139
145
|
/**
|
|
140
146
|
* Execute a run (internal method).
|
|
141
147
|
*/
|
|
@@ -163,6 +169,7 @@ declare class WorkflowEngine {
|
|
|
163
169
|
* @throws {RunNotFoundError} If no run with the given ID exists
|
|
164
170
|
*/
|
|
165
171
|
cancelRun(runId: string): Promise<void>;
|
|
172
|
+
private cancelTrackedRun;
|
|
166
173
|
/**
|
|
167
174
|
* Get the current status of a run.
|
|
168
175
|
*
|
|
@@ -603,4 +610,5 @@ declare class PostgresSchedulePersistence implements SchedulePersistence {
|
|
|
603
610
|
private rowToSchedule;
|
|
604
611
|
}
|
|
605
612
|
|
|
606
|
-
export { CronScheduler as C, PostgresSchedulePersistence as P, SQLiteSchedulePersistence as S,
|
|
613
|
+
export { CronScheduler as C, PostgresSchedulePersistence as P, SQLiteSchedulePersistence as S, WorkflowEngine as W };
|
|
614
|
+
export type { TriggerType as T, CronSchedulerConfig as a, PostgresSchedulePersistenceConfig as b, SQLiteSchedulePersistenceConfig as c, SchedulePersistence as d, Scheduler as e, StartRunOptions as f, WorkflowEngineConfig as g, WorkflowSchedule as h };
|