@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
package/CHANGELOG.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
- CI workflow for automated build, typecheck, and test on pull requests
|
|
12
|
-
- Security scanning workflow with weekly `npm audit` checks
|
|
13
|
-
- Dependabot configuration for automated dependency and GitHub Actions updates
|
|
14
|
-
- `CODEOWNERS` file requiring designated reviewers for security-sensitive paths
|
|
15
|
-
- `SECURITY.md` with vulnerability reporting instructions and disclosure policy
|
|
16
|
-
- Branch protection documentation in README
|
|
17
|
-
|
|
18
|
-
### Changed
|
|
19
|
-
- Pinned GitHub Actions in `publish.yml` to full commit SHAs to prevent supply-chain attacks via tag hijacking
|
|
20
|
-
|
|
21
|
-
## [0.2.6] - 2026-04-03
|
|
22
|
-
|
|
23
|
-
### Added
|
|
24
|
-
- Initial public release
|
|
25
|
-
- Durable workflow execution engine with step orchestration
|
|
26
|
-
- SQLite and PostgreSQL storage backends
|
|
27
|
-
- In-memory storage for testing
|
|
28
|
-
- Cron-based scheduling with SQLite and PostgreSQL persistence
|
|
29
|
-
- Workflow completion triggers
|
|
30
|
-
- Socket.IO and webhook event transports with HMAC-SHA256 signing
|
|
31
|
-
- SSRF protection for webhook URLs with DNS-rebinding prevention
|
|
32
|
-
- Concurrency control with priority queues
|
|
33
|
-
- Rule-based planning system for dynamic workflow generation
|
|
34
|
-
- Full TypeScript support with type inference
|
package/SECURITY.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Supported Versions
|
|
4
|
-
|
|
5
|
-
| Version | Supported |
|
|
6
|
-
| ------- | ------------------ |
|
|
7
|
-
| 0.2.x | Yes |
|
|
8
|
-
| < 0.2 | No |
|
|
9
|
-
|
|
10
|
-
## Reporting a Vulnerability
|
|
11
|
-
|
|
12
|
-
If you discover a security vulnerability in Stepflow, please report it responsibly.
|
|
13
|
-
|
|
14
|
-
**Do not open a public GitHub issue for security vulnerabilities.**
|
|
15
|
-
|
|
16
|
-
Instead, please email **security@multiplier-labs.com** with:
|
|
17
|
-
|
|
18
|
-
- A description of the vulnerability
|
|
19
|
-
- Steps to reproduce the issue
|
|
20
|
-
- The potential impact
|
|
21
|
-
- Any suggested remediation (optional)
|
|
22
|
-
|
|
23
|
-
We will acknowledge receipt within **48 hours** and aim to provide an initial assessment within **5 business days**.
|
|
24
|
-
|
|
25
|
-
## Disclosure Policy
|
|
26
|
-
|
|
27
|
-
- We follow coordinated disclosure. Please allow us reasonable time to address the issue before public disclosure.
|
|
28
|
-
- Once a fix is released, we will publish a security advisory via GitHub Security Advisories.
|
|
29
|
-
- Contributors who report valid vulnerabilities will be credited in the advisory (unless they prefer anonymity).
|
|
30
|
-
|
|
31
|
-
## Security Practices
|
|
32
|
-
|
|
33
|
-
This project follows these security practices:
|
|
34
|
-
|
|
35
|
-
- **Signed commits** are encouraged for all contributors
|
|
36
|
-
- **Branch protection** is enforced on `main` with required reviews and status checks
|
|
37
|
-
- **Dependency scanning** via Dependabot with automated PRs for security updates
|
|
38
|
-
- **npm audit** runs in CI on every pull request
|
|
39
|
-
- **Secret scanning** and **push protection** are enabled on the repository
|
|
40
|
-
- **CODEOWNERS** requires review from designated maintainers for security-sensitive code paths
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage/memory.ts","../src/storage/sqlite-core.ts","../src/storage/sqlite.ts","../src/storage/postgres-core.ts","../src/storage/postgres.ts"],"sourcesContent":["/**\n * In-memory storage adapter for development and testing.\n * All data is lost when the process exits.\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 { generateId } from '../utils/id';\nimport type {\n StorageAdapter,\n WorkflowRunRecord,\n WorkflowRunStepRecord,\n WorkflowEventRecord,\n ListRunsOptions,\n ListEventsOptions,\n PaginatedResult,\n} from './types';\n\n/**\n * In-memory implementation of StorageAdapter.\n * Useful for development, testing, and lightweight deployments.\n *\n * @example\n * ```typescript\n * import { WorkflowEngine } from 'stepflow';\n * import { MemoryStorageAdapter } from 'stepflow/storage';\n *\n * const storage = new MemoryStorageAdapter();\n * const engine = new WorkflowEngine({ storage });\n * ```\n */\nexport class MemoryStorageAdapter implements StorageAdapter {\n private runs = new Map<string, WorkflowRunRecord>();\n private steps = new Map<string, WorkflowRunStepRecord>();\n private events = new Map<string, WorkflowEventRecord>();\n\n // ============================================================================\n // Run Operations\n // ============================================================================\n\n /** Create and persist a new workflow run record. */\n async createRun(run: Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {\n const record: WorkflowRunRecord = {\n ...run,\n id: generateId(),\n createdAt: new Date(),\n };\n this.runs.set(record.id, record);\n return record;\n }\n\n /** Retrieve a workflow run by ID, or null if not found. */\n async getRun(runId: string): Promise<WorkflowRunRecord | null> {\n return this.runs.get(runId) ?? null;\n }\n\n /** Apply partial updates to an existing workflow run. No-op if the run does not exist. */\n async updateRun(runId: string, updates: Partial<WorkflowRunRecord>): Promise<void> {\n const run = this.runs.get(runId);\n if (run) {\n Object.assign(run, updates);\n }\n }\n\n /** List workflow runs with optional filtering, sorting, and pagination. */\n async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {\n let items = Array.from(this.runs.values());\n\n // Filter by kind\n if (options.kind) {\n items = items.filter(r => r.kind === options.kind);\n }\n\n // Filter by status\n if (options.status) {\n const statuses = Array.isArray(options.status) ? options.status : [options.status];\n items = items.filter(r => statuses.includes(r.status));\n }\n\n // Filter by parentRunId\n if (options.parentRunId !== undefined) {\n items = items.filter(r => r.parentRunId === options.parentRunId);\n }\n\n // Sort\n const orderBy = options.orderBy ?? 'createdAt';\n const direction = options.orderDirection ?? 'desc';\n items.sort((a, b) => {\n const aVal = a[orderBy]?.getTime() ?? 0;\n const bVal = b[orderBy]?.getTime() ?? 0;\n return direction === 'asc' ? aVal - bVal : bVal - aVal;\n });\n\n const total = items.length;\n const offset = options.offset ?? 0;\n const limit = options.limit ?? 50;\n\n items = items.slice(offset, offset + limit);\n\n return { items, total, limit, offset };\n }\n\n // ============================================================================\n // Step Operations\n // ============================================================================\n\n /** Create and persist a new step execution record. */\n async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {\n const record: WorkflowRunStepRecord = {\n ...step,\n id: generateId(),\n };\n this.steps.set(record.id, record);\n return record;\n }\n\n /** Retrieve a step record by ID, or null if not found. */\n async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {\n return this.steps.get(stepId) ?? null;\n }\n\n /** Apply partial updates to an existing step record. No-op if the step does not exist. */\n async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {\n const step = this.steps.get(stepId);\n if (step) {\n Object.assign(step, updates);\n }\n }\n\n /** Retrieve all step records for a workflow run, ordered by start time ascending. */\n async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {\n return Array.from(this.steps.values())\n .filter(s => s.runId === runId)\n .sort((a, b) => (a.startedAt?.getTime() ?? 0) - (b.startedAt?.getTime() ?? 0));\n }\n\n // ============================================================================\n // Event Operations\n // ============================================================================\n\n /** Persist a workflow event record. */\n async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {\n const record: WorkflowEventRecord = {\n ...event,\n id: generateId(),\n };\n this.events.set(record.id, record);\n }\n\n /** Retrieve events for a workflow run with optional filtering and pagination. */\n async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {\n let items = Array.from(this.events.values()).filter(e => e.runId === runId);\n\n // Filter by step\n if (options.stepKey) {\n items = items.filter(e => e.stepKey === options.stepKey);\n }\n\n // Filter by level\n if (options.level) {\n items = items.filter(e => e.level === options.level);\n }\n\n // Sort by timestamp\n items.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());\n\n // Apply pagination\n const offset = options.offset ?? 0;\n const limit = options.limit ?? 100;\n\n return items.slice(offset, offset + limit);\n }\n\n // ============================================================================\n // Optional Operations\n // ============================================================================\n\n /** Delete runs (and their associated steps and events) created before the given date. Returns the number of deleted runs. */\n async deleteOldRuns(olderThan: Date): Promise<number> {\n let deleted = 0;\n for (const [id, run] of this.runs) {\n if (run.createdAt < olderThan) {\n // Delete associated steps\n for (const [stepId, step] of this.steps) {\n if (step.runId === id) {\n this.steps.delete(stepId);\n }\n }\n // Delete associated events\n for (const [eventId, event] of this.events) {\n if (event.runId === id) {\n this.events.delete(eventId);\n }\n }\n // Delete run\n this.runs.delete(id);\n deleted++;\n }\n }\n return deleted;\n }\n\n // ============================================================================\n // Testing Utilities\n // ============================================================================\n\n /**\n * Clear all stored data. Useful for testing.\n */\n clear(): void {\n this.runs.clear();\n this.steps.clear();\n this.events.clear();\n }\n\n /**\n * Get counts of stored records. Useful for testing.\n */\n getStats(): { runs: number; steps: number; events: number } {\n return {\n runs: this.runs.size,\n steps: this.steps.size,\n events: this.events.size,\n };\n }\n}\n","/**\n * Shared core for the SQLite storage adapter.\n *\n * Mirrors the structure introduced for Postgres in `postgres-core.ts` (PR #50)\n * so all storage adapters expose row mappers and types in the same shape.\n *\n * The SQLite case is simpler than Postgres: there is only one adapter class\n * (`SQLiteStorageAdapter`), with no transaction-scoped twin. So this module\n * carries only the *pure* portion of the postgres-core split:\n *\n * - SQLite row type interfaces for the three core tables\n * (`workflow_runs`, `workflow_run_steps`, `workflow_events`).\n * - Pure row mappers (`mapRunRow`, `mapStepRow`, `mapEventRow`) that take\n * a `MapperContext = { component, logger }` and route every JSON-column\n * parse through the shared `safeParseField` / `safeParseOptionalField`\n * helpers. Corruption is reported via the configured logger with safe\n * metadata only — the raw value is never logged (audit M2 / L3, PR #52).\n *\n * No abstract base class is needed: the adapter class binds the prepared\n * statements + better-sqlite3 connection in one place and would gain nothing\n * from inheritance. The mappers and row types are extracted purely so they\n * are reusable, testable, and consistent with `postgres-core.ts`.\n */\n\nimport {\n safeParseField as sharedSafeParseField,\n safeParseOptionalField as sharedSafeParseOptionalField,\n type MapperContext,\n type SafeJsonParseContext,\n} from '../utils/safe-json';\nimport type {\n WorkflowRunRecord,\n WorkflowRunStepRecord,\n WorkflowEventRecord,\n} from './types';\nimport type { RunStatus, StepStatus, WorkflowError } from '../core/types';\n\n/**\n * Re-export the shared `MapperContext` so callers can `import { MapperContext }`\n * from either storage core module.\n */\nexport type { MapperContext };\n\n// ============================================================================\n// SQLite row types\n// ============================================================================\n//\n// SQLite stores everything as text/integer/real, so timestamps come back as\n// ISO-8601 strings (the adapter writes them via `Date.prototype.toISOString`)\n// and JSON columns come back as the original `JSON.stringify` output. The\n// mappers below convert each row back to the public record shape.\n\n/** Row shape for the `workflow_runs` table. */\nexport interface SQLiteRunRow {\n id: string;\n kind: string;\n status: string;\n parent_run_id: string | null;\n input_json: string;\n metadata_json: string;\n context_json: string;\n completed_steps_json: string | null;\n error_json: string | null;\n created_at: string;\n started_at: string | null;\n finished_at: string | null;\n}\n\n/** Row shape for the `workflow_run_steps` table. */\nexport interface SQLiteStepRow {\n id: string;\n run_id: string;\n step_key: string;\n step_name: string;\n status: string;\n attempt: number;\n result_json: string | null;\n error_json: string | null;\n started_at: string | null;\n finished_at: string | null;\n}\n\n/** Row shape for the `workflow_events` table. */\nexport interface SQLiteEventRow {\n id: string;\n run_id: string;\n step_key: string | null;\n event_type: string;\n level: string;\n payload_json: string | null;\n timestamp: string;\n}\n\n// ============================================================================\n// Row mappers (pure)\n// ============================================================================\n//\n// Adapters supply a `MapperContext` ({ component, logger }); each helper\n// extends it with the row id and column being parsed so a structured log\n// emitted on corruption identifies the bad row. The raw value is never\n// logged — see `src/utils/safe-json.ts`.\n\n/** Map a `workflow_runs` row to the public `WorkflowRunRecord` shape. */\nexport function mapRunRow(row: SQLiteRunRow, ctx: MapperContext): WorkflowRunRecord {\n const fieldCtx = (column: string): SafeJsonParseContext => ({ ...ctx, rowId: row.id, column });\n return {\n id: row.id,\n kind: row.kind,\n status: row.status as RunStatus,\n parentRunId: row.parent_run_id ?? undefined,\n input: sharedSafeParseField(row.input_json, {}, fieldCtx('input_json')) as Record<string, unknown>,\n metadata: sharedSafeParseField(row.metadata_json, {}, fieldCtx('metadata_json')) as Record<string, unknown>,\n context: sharedSafeParseField(row.context_json, {}, fieldCtx('context_json')) as Record<string, unknown>,\n completedSteps: sharedSafeParseOptionalField(row.completed_steps_json, fieldCtx('completed_steps_json')) as string[] | undefined,\n error: sharedSafeParseOptionalField(row.error_json, fieldCtx('error_json')) as WorkflowError | undefined,\n createdAt: new Date(row.created_at),\n startedAt: row.started_at ? new Date(row.started_at) : undefined,\n finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,\n };\n}\n\n/** Map a `workflow_run_steps` row to the public `WorkflowRunStepRecord` shape. */\nexport function mapStepRow(row: SQLiteStepRow, ctx: MapperContext): WorkflowRunStepRecord {\n const fieldCtx = (column: string): SafeJsonParseContext => ({ ...ctx, rowId: row.id, column });\n return {\n id: row.id,\n runId: row.run_id,\n stepKey: row.step_key,\n stepName: row.step_name,\n status: row.status as StepStatus,\n attempt: row.attempt,\n result: sharedSafeParseOptionalField(row.result_json, fieldCtx('result_json')),\n error: sharedSafeParseOptionalField(row.error_json, fieldCtx('error_json')) as WorkflowError | undefined,\n startedAt: row.started_at ? new Date(row.started_at) : undefined,\n finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,\n };\n}\n\n/** Map a `workflow_events` row to the public `WorkflowEventRecord` shape. */\nexport function mapEventRow(row: SQLiteEventRow, ctx: MapperContext): WorkflowEventRecord {\n const fieldCtx: SafeJsonParseContext = { ...ctx, rowId: row.id, column: 'payload_json' };\n return {\n id: row.id,\n runId: row.run_id,\n stepKey: row.step_key ?? undefined,\n eventType: row.event_type,\n level: row.level as 'info' | 'warn' | 'error',\n payload: sharedSafeParseOptionalField(row.payload_json, fieldCtx),\n timestamp: new Date(row.timestamp),\n };\n}\n","/**\n * SQLite storage adapter using better-sqlite3.\n *\n * Provides durable persistence for workflow runs, steps, and events.\n *\n * ## Module layout\n *\n * - `SQLiteStorageAdapter` (this file) owns the better-sqlite3 connection,\n * the prepared-statement cache, and every method on `StorageAdapter`. It\n * is the only adapter class — unlike the Postgres adapter there is no\n * transaction-scoped twin, so no abstract CRUD base class is required.\n *\n * - `sqlite-core.ts` holds the pure row-type interfaces and row mappers\n * (`mapRunRow`, `mapStepRow`, `mapEventRow`). The mappers route every\n * JSON-column parse through `safeParseField` / `safeParseOptionalField`\n * in `src/utils/safe-json.ts`, sharing the corruption-counter +\n * structured-warn behavior with the Postgres core (audit M2 / L3,\n * PR #52). The split mirrors `postgres-core.ts` so both adapters\n * expose mappers in the same shape.\n */\n\nimport type Database from 'better-sqlite3';\nimport { generateId } from '../utils/id';\nimport type {\n StorageAdapter,\n WorkflowRunRecord,\n WorkflowRunStepRecord,\n WorkflowEventRecord,\n ListRunsOptions,\n ListEventsOptions,\n PaginatedResult,\n} from './types';\nimport type { Logger } from '../core/types';\nimport { sanitizeErrorForStorage } from '../utils/logger';\nimport type { MapperContext } from '../utils/safe-json';\nimport {\n mapRunRow,\n mapStepRow,\n mapEventRow,\n type SQLiteRunRow,\n type SQLiteStepRow,\n type SQLiteEventRow,\n} from './sqlite-core';\n\n// ============================================================================\n// Schema SQL\n// ============================================================================\n\n/**\n * SQL statements to create the workflow tables.\n * These create the workflow tables in your database.\n */\nconst CREATE_TABLES_SQL = `\n-- Workflow runs table\nCREATE TABLE IF NOT EXISTS workflow_runs (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n status TEXT NOT NULL,\n parent_run_id TEXT,\n input_json TEXT NOT NULL,\n metadata_json TEXT NOT NULL,\n context_json TEXT NOT NULL,\n completed_steps_json TEXT,\n error_json TEXT,\n created_at TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_workflow_runs_kind_status ON workflow_runs(kind, status);\nCREATE INDEX IF NOT EXISTS idx_workflow_runs_parent ON workflow_runs(parent_run_id);\nCREATE INDEX IF NOT EXISTS idx_workflow_runs_created ON workflow_runs(created_at);\nCREATE INDEX IF NOT EXISTS idx_workflow_runs_status ON workflow_runs(status);\n\n-- Workflow run steps table\nCREATE TABLE IF NOT EXISTS workflow_run_steps (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL,\n step_key TEXT NOT NULL,\n step_name TEXT,\n status TEXT NOT NULL,\n attempt INTEGER NOT NULL DEFAULT 1,\n result_json TEXT,\n error_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run ON workflow_run_steps(run_id);\nCREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run_key ON workflow_run_steps(run_id, step_key);\n\n-- Workflow events table\nCREATE TABLE IF NOT EXISTS workflow_events (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL,\n step_key TEXT,\n event_type TEXT NOT NULL,\n level TEXT NOT NULL,\n payload_json TEXT,\n timestamp TEXT NOT NULL,\n FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_workflow_events_run ON workflow_events(run_id);\nCREATE INDEX IF NOT EXISTS idx_workflow_events_run_ts ON workflow_events(run_id, timestamp);\n`;\n\n/**\n * Columns added to {@link CREATE_TABLES_SQL} after the initial schema, expressed\n * as `ALTER TABLE ADD COLUMN` migrations. SQLite's `CREATE TABLE IF NOT EXISTS`\n * does not reconcile column lists against existing tables, so a deploy that ran\n * an older version of this adapter will be missing any column added later. The\n * adapter applies these on startup so existing databases stay usable across\n * upgrades.\n *\n * Append-only: SQLite does not support DROP COLUMN or general ALTER COLUMN, so\n * removing or changing an entry here would break upgrades from older versions.\n * Only add new entries when a column is added to {@link CREATE_TABLES_SQL}.\n */\nconst SCHEMA_MIGRATIONS: ReadonlyArray<{ table: string; column: string; type: string }> = [\n { table: 'workflow_runs', column: 'completed_steps_json', type: 'TEXT' },\n];\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Configuration options for the SQLite storage adapter.\n */\nexport interface SQLiteStorageConfig {\n /**\n * The better-sqlite3 database instance.\n * Can be an in-memory database (`:memory:`) or a file path.\n */\n db: Database.Database;\n\n /**\n * Whether to automatically create tables on initialization.\n * Default: true\n */\n autoCreateTables?: boolean;\n\n /**\n * @deprecated This option is not currently supported and will be ignored.\n * Table names are always prefixed with 'workflow_'.\n */\n tablePrefix?: 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// Helpers\n// ============================================================================\n\n/**\n * better-sqlite3 raises `SqliteError { code: 'SQLITE_ERROR', message:\n * 'duplicate column name: <name>' }` when an `ALTER TABLE ADD COLUMN` targets\n * a column that already exists. Treat that as a benign no-op so concurrent\n * startups (or any race between the {@link runMigrations} existence check and\n * the ALTER) don't crash. All other errors propagate.\n */\nfunction isDuplicateColumnError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const code = (err as { code?: unknown }).code;\n const message = (err as { message?: unknown }).message;\n return (\n code === 'SQLITE_ERROR' &&\n typeof message === 'string' &&\n message.toLowerCase().includes('duplicate column name')\n );\n}\n\n/**\n * Resolve to `true` if `p` settles within a few microtask ticks of being\n * inspected, otherwise `false`. Used by the deprecated async `transaction()`\n * to detect callbacks that suspend on real async I/O — by the time we ask,\n * the synchronous COMMIT has already run, so a still-pending promise means\n * any post-await operations land outside the transaction.\n */\nasync function isPromiseSettled(p: Promise<unknown>): Promise<boolean> {\n let settled = false;\n p.then(\n () => {\n settled = true;\n },\n () => {\n settled = true;\n }\n );\n // Drain a handful of microtask ticks. `await Promise.resolve()` callbacks\n // and other already-resolved continuations finish in 1-2 ticks; anything\n // still pending after this is doing real async I/O (timers, network, etc.).\n for (let i = 0; i < 5 && !settled; i++) {\n await Promise.resolve();\n }\n return settled;\n}\n\n// ============================================================================\n// SQLite Storage Adapter\n// ============================================================================\n\n/**\n * SQLite implementation of StorageAdapter using better-sqlite3.\n *\n * Features:\n * - Synchronous operations (better-sqlite3 is sync by design)\n * - Automatic table creation\n * - Transaction support\n * - Customizable table prefix\n *\n * @example\n * ```typescript\n * import Database from 'better-sqlite3';\n * import { SQLiteStorageAdapter } from 'stepflow/storage';\n *\n * const db = new Database('./workflows.db');\n * const storage = new SQLiteStorageAdapter({ db });\n *\n * const engine = new WorkflowEngine({ storage });\n * ```\n */\nexport class SQLiteStorageAdapter implements StorageAdapter {\n private db: Database.Database;\n private prefix: string;\n private logger: Logger | undefined;\n\n // Prepared statements (cached for performance)\n private stmts: {\n insertRun: Database.Statement;\n getRun: Database.Statement;\n updateRun: Database.Statement;\n listRuns: Database.Statement;\n countRuns: Database.Statement;\n insertStep: Database.Statement;\n getStep: Database.Statement;\n updateStep: Database.Statement;\n getStepsForRun: Database.Statement;\n insertEvent: Database.Statement;\n getEventsForRun: Database.Statement;\n deleteOldRuns: Database.Statement;\n deleteStepsForRuns: Database.Statement;\n deleteEventsForRuns: Database.Statement;\n getInterruptedRuns: Database.Statement;\n getLastCompletedStep: Database.Statement;\n } | null = null;\n\n constructor(config: SQLiteStorageConfig) {\n this.db = config.db;\n this.prefix = config.tablePrefix ?? 'workflow';\n this.logger = config.logger;\n\n // Enable foreign keys\n this.db.pragma('foreign_keys = ON');\n\n // Create tables if needed\n if (config.autoCreateTables !== false) {\n this.createTables();\n }\n\n // Always reconcile column-level migrations: the prepared statements below\n // reference the latest schema, so an adapter sitting on top of an older\n // table layout (e.g., a database created by a previous version, or a\n // caller-managed schema with `autoCreateTables: false` that has not been\n // hand-upgraded) would crash on the first INSERT/SELECT. Migrations are\n // idempotent and skip tables that don't exist.\n this.runMigrations();\n\n // Prepare statements\n this.prepareStatements();\n }\n\n /**\n * Create the workflow tables if they don't exist.\n */\n private createTables(): void {\n this.db.exec(CREATE_TABLES_SQL);\n }\n\n /**\n * Apply additive schema migrations to existing tables.\n *\n * `CREATE TABLE IF NOT EXISTS` is a no-op when the table already exists, so\n * any column added to {@link CREATE_TABLES_SQL} after the initial schema is\n * silently absent on databases created by older versions of this adapter.\n * Without this step, the first INSERT/SELECT crashes with\n * `SqliteError: table X has no column named Y`. This mirrors the\n * `ALTER TABLE ADD COLUMN IF NOT EXISTS` block in `postgres.ts`'s\n * `createTables()`.\n *\n * For each entry in {@link SCHEMA_MIGRATIONS}: if the table exists and the\n * column does not, run `ALTER TABLE ADD COLUMN`. The ALTER is wrapped in a\n * try/catch that tolerates `duplicate column` errors so concurrent startups\n * (or any race between the existence check and the ALTER) do not crash.\n * Other SQLite errors propagate.\n */\n private runMigrations(): void {\n for (const { table, column, type } of SCHEMA_MIGRATIONS) {\n const tableInfo = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;\n // PRAGMA table_info on a missing table returns []; CREATE TABLE handles that case.\n if (tableInfo.length === 0) continue;\n if (tableInfo.some((col) => col.name === column)) continue;\n\n try {\n this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);\n } catch (err) {\n if (isDuplicateColumnError(err)) continue;\n throw err;\n }\n }\n }\n\n /**\n * Prepare all SQL statements for better performance.\n */\n private prepareStatements(): void {\n this.stmts = {\n insertRun: this.db.prepare(`\n INSERT INTO workflow_runs (id, kind, status, parent_run_id, input_json, metadata_json, context_json, completed_steps_json, error_json, created_at, started_at, finished_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `),\n getRun: this.db.prepare(`\n SELECT * FROM workflow_runs WHERE id = ?\n `),\n updateRun: this.db.prepare(`\n UPDATE workflow_runs\n SET status = COALESCE(?, status),\n context_json = COALESCE(?, context_json),\n completed_steps_json = COALESCE(?, completed_steps_json),\n error_json = COALESCE(?, error_json),\n started_at = COALESCE(?, started_at),\n finished_at = COALESCE(?, finished_at)\n WHERE id = ?\n `),\n // json_each() is a SQLite table-valued function that expands a JSON array into rows.\n // We use it here to support filtering by multiple statuses in a single prepared statement:\n // the caller passes a JSON array like '[\"running\",\"failed\"]', and json_each unpacks it\n // so the IN clause can match against each value. When status is NULL, the entire\n // condition is bypassed via the (? IS NULL OR ...) guard.\n listRuns: this.db.prepare(`\n SELECT * FROM workflow_runs\n WHERE (? IS NULL OR kind = ?)\n AND (? IS NULL OR status IN (SELECT value FROM json_each(?)))\n AND (? IS NULL OR parent_run_id = ?)\n ORDER BY created_at DESC\n LIMIT ? OFFSET ?\n `),\n countRuns: this.db.prepare(`\n SELECT COUNT(*) as count FROM workflow_runs\n WHERE (? IS NULL OR kind = ?)\n AND (? IS NULL OR status IN (SELECT value FROM json_each(?)))\n AND (? IS NULL OR parent_run_id = ?)\n `),\n insertStep: this.db.prepare(`\n INSERT INTO workflow_run_steps (id, run_id, step_key, step_name, status, attempt, result_json, error_json, started_at, finished_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `),\n getStep: this.db.prepare(`\n SELECT * FROM workflow_run_steps WHERE id = ?\n `),\n updateStep: this.db.prepare(`\n UPDATE workflow_run_steps\n SET status = COALESCE(?, status),\n attempt = COALESCE(?, attempt),\n result_json = COALESCE(?, result_json),\n error_json = COALESCE(?, error_json),\n finished_at = COALESCE(?, finished_at)\n WHERE id = ?\n `),\n getStepsForRun: this.db.prepare(`\n SELECT * FROM workflow_run_steps WHERE run_id = ? ORDER BY started_at ASC\n `),\n insertEvent: this.db.prepare(`\n INSERT INTO workflow_events (id, run_id, step_key, event_type, level, payload_json, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `),\n getEventsForRun: this.db.prepare(`\n SELECT * FROM workflow_events\n WHERE run_id = ?\n AND (? IS NULL OR step_key = ?)\n AND (? IS NULL OR level = ?)\n ORDER BY timestamp ASC\n LIMIT ? OFFSET ?\n `),\n deleteOldRuns: this.db.prepare(`\n DELETE FROM workflow_runs WHERE created_at < ?\n `),\n deleteStepsForRuns: this.db.prepare(`\n DELETE FROM workflow_run_steps WHERE run_id IN (SELECT id FROM workflow_runs WHERE created_at < ?)\n `),\n deleteEventsForRuns: this.db.prepare(`\n DELETE FROM workflow_events WHERE run_id IN (SELECT id FROM workflow_runs WHERE created_at < ?)\n `),\n getInterruptedRuns: this.db.prepare(`\n SELECT * FROM workflow_runs\n WHERE status IN ('queued', 'running')\n ORDER BY created_at ASC\n `),\n getLastCompletedStep: this.db.prepare(`\n SELECT * FROM workflow_run_steps\n WHERE run_id = ? AND status = 'succeeded'\n ORDER BY finished_at DESC\n LIMIT 1\n `),\n };\n }\n\n // ============================================================================\n // Run Operations\n // ============================================================================\n\n async createRun(run: Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {\n const id = generateId();\n const createdAt = new Date();\n\n this.stmts!.insertRun.run(\n id,\n run.kind,\n run.status,\n run.parentRunId ?? null,\n JSON.stringify(run.input),\n JSON.stringify(run.metadata),\n JSON.stringify(run.context),\n run.completedSteps ? JSON.stringify(run.completedSteps) : null,\n run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,\n createdAt.toISOString(),\n run.startedAt?.toISOString() ?? null,\n run.finishedAt?.toISOString() ?? null\n );\n\n return {\n ...run,\n id,\n createdAt,\n };\n }\n\n async getRun(runId: string): Promise<WorkflowRunRecord | null> {\n const row = this.stmts!.getRun.get(runId) as SQLiteRunRow | undefined;\n return row ? mapRunRow(row, this.mapperCtx) : null;\n }\n\n async updateRun(runId: string, updates: Partial<WorkflowRunRecord>): Promise<void> {\n this.stmts!.updateRun.run(\n updates.status ?? null,\n updates.context ? JSON.stringify(updates.context) : null,\n updates.completedSteps ? JSON.stringify(updates.completedSteps) : null,\n updates.error ? JSON.stringify(sanitizeErrorForStorage(updates.error)) : null,\n updates.startedAt?.toISOString() ?? null,\n updates.finishedAt?.toISOString() ?? null,\n runId\n );\n }\n\n async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n const kind = options.kind ?? null;\n const status = options.status ?? null;\n const statusJson = status ? JSON.stringify(Array.isArray(status) ? status : [status]) : null;\n const parentRunId = options.parentRunId ?? null;\n\n const rows = this.stmts!.listRuns.all(\n kind, kind,\n statusJson, statusJson,\n parentRunId, parentRunId,\n limit, offset\n ) as SQLiteRunRow[];\n\n const countResult = this.stmts!.countRuns.get(\n kind, kind,\n statusJson, statusJson,\n parentRunId, parentRunId\n ) as { count: number };\n\n return {\n items: rows.map(row => mapRunRow(row, this.mapperCtx)),\n total: countResult.count,\n limit,\n offset,\n };\n }\n\n // ============================================================================\n // Step Operations\n // ============================================================================\n\n async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {\n const id = generateId();\n\n this.stmts!.insertStep.run(\n id,\n step.runId,\n step.stepKey,\n step.stepName,\n step.status,\n step.attempt,\n step.result !== undefined ? JSON.stringify(step.result) : null,\n step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,\n step.startedAt?.toISOString() ?? null,\n step.finishedAt?.toISOString() ?? null\n );\n\n return { ...step, id };\n }\n\n async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {\n const row = this.stmts!.getStep.get(stepId) as SQLiteStepRow | undefined;\n return row ? mapStepRow(row, this.mapperCtx) : null;\n }\n\n async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {\n this.stmts!.updateStep.run(\n updates.status ?? null,\n updates.attempt ?? null,\n updates.result !== undefined ? JSON.stringify(updates.result) : null,\n updates.error ? JSON.stringify(sanitizeErrorForStorage(updates.error)) : null,\n updates.finishedAt?.toISOString() ?? null,\n stepId\n );\n }\n\n async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {\n const rows = this.stmts!.getStepsForRun.all(runId) as SQLiteStepRow[];\n return rows.map(row => mapStepRow(row, this.mapperCtx));\n }\n\n // ============================================================================\n // Event Operations\n // ============================================================================\n\n async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {\n const id = generateId();\n\n this.stmts!.insertEvent.run(\n id,\n event.runId,\n event.stepKey ?? null,\n event.eventType,\n event.level,\n event.payload !== undefined ? JSON.stringify(event.payload) : null,\n event.timestamp.toISOString()\n );\n }\n\n async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {\n const limit = options.limit ?? 1000;\n const offset = options.offset ?? 0;\n const stepKey = options.stepKey ?? null;\n const level = options.level ?? null;\n\n const rows = this.stmts!.getEventsForRun.all(\n runId,\n stepKey, stepKey,\n level, level,\n limit, offset\n ) as SQLiteEventRow[];\n\n return rows.map(row => mapEventRow(row, this.mapperCtx));\n }\n\n // ============================================================================\n // Transaction Support\n // ============================================================================\n\n /**\n * Execute a function within a database transaction (async interface).\n *\n * @deprecated Use {@link transactionSync} instead. better-sqlite3 transactions\n * are synchronous: COMMIT happens the moment the callback returns control to\n * `db.transaction(fn)()`. If the callback awaits real async I/O (network,\n * timers, etc.), the COMMIT has already happened by the time the awaited work\n * runs, so its operations are **not** transactional.\n *\n * To make the footgun visible the adapter installs a runtime guard: when the\n * callback's promise has not settled by the time the COMMIT returns, the\n * deprecated method emits a `console.warn` so that the misuse surfaces in\n * production logs. The promise is still returned so existing callers keep\n * working — switch to `transactionSync` to silence the warning.\n */\n async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {\n let fnPromise!: Promise<T>;\n this.transactionSync(() => {\n fnPromise = fn(this);\n });\n\n // COMMIT has already executed at this point. If the callback's promise is\n // still pending, the callback is doing async work outside the transaction\n // boundary — log a warning so the misuse is visible in production logs.\n if (!(await isPromiseSettled(fnPromise))) {\n console.warn(\n '[SQLiteStorageAdapter] transaction() callback did not settle synchronously. ' +\n 'better-sqlite3 transactions are synchronous and the COMMIT has already executed; ' +\n 'any async work inside the callback is NOT transactional. ' +\n 'Use transactionSync() with purely synchronous operations instead.'\n );\n }\n\n return fnPromise;\n }\n\n /**\n * Execute a synchronous transaction (preferred for better-sqlite3).\n * Use this when you need transaction guarantees.\n *\n * @example\n * ```typescript\n * storage.transactionSync(() => {\n * // Multiple operations in a single transaction\n * const run = storage.createRunSync(...);\n * storage.updateRunSync(run.id, ...);\n * });\n * ```\n */\n transactionSync<T>(fn: () => T): T {\n return this.db.transaction(fn)();\n }\n\n // ============================================================================\n // Cleanup Operations\n // ============================================================================\n\n /**\n * Delete runs older than the specified date.\n * Also deletes associated steps and events (via CASCADE).\n */\n async deleteOldRuns(olderThan: Date): Promise<number> {\n const isoDate = olderThan.toISOString();\n\n // Due to CASCADE, steps and events are deleted automatically\n // But we do it explicitly for databases without CASCADE support\n return this.db.transaction(() => {\n this.stmts!.deleteEventsForRuns.run(isoDate);\n this.stmts!.deleteStepsForRuns.run(isoDate);\n const result = this.stmts!.deleteOldRuns.run(isoDate);\n return result.changes;\n })();\n }\n\n // ============================================================================\n // Resume Support\n // ============================================================================\n\n /**\n * Get all runs that were interrupted (status is 'running' or 'queued').\n * These runs can potentially be resumed.\n */\n async getInterruptedRuns(): Promise<WorkflowRunRecord[]> {\n const rows = this.stmts!.getInterruptedRuns.all() as SQLiteRunRow[];\n return rows.map(row => mapRunRow(row, this.mapperCtx));\n }\n\n /**\n * Get the last completed step for a run.\n * Useful for resuming from a checkpoint.\n */\n async getLastCompletedStep(runId: string): Promise<WorkflowRunStepRecord | null> {\n const row = this.stmts!.getLastCompletedStep.get(runId) as SQLiteStepRow | undefined;\n return row ? mapStepRow(row, this.mapperCtx) : null;\n }\n\n // ============================================================================\n // Row Mapping\n // ============================================================================\n //\n // The pure row mappers (`mapRunRow`, `mapStepRow`, `mapEventRow`) live in\n // `./sqlite-core.ts`. Each call site here builds a `MapperContext` once via\n // `this.mapperCtx` and hands it to the mapper, which extends it with\n // `rowId` + `column` per JSON-parse so corruption logs identify the bad row\n // (audit M2 / L3 — PR #52).\n\n /** Mapper context paired with the adapter's component label + optional logger. */\n private get mapperCtx(): MapperContext {\n return { component: 'SQLiteStorageAdapter', logger: this.logger };\n }\n\n // ============================================================================\n // Utility Methods\n // ============================================================================\n\n /**\n * Close the database connection.\n */\n close(): void {\n this.db.close();\n }\n\n /**\n * Get database statistics.\n */\n getStats(): { runs: number; steps: number; events: number } {\n const runsCount = this.db.prepare('SELECT COUNT(*) as count FROM workflow_runs').get() as { count: number };\n const stepsCount = this.db.prepare('SELECT COUNT(*) as count FROM workflow_run_steps').get() as { count: number };\n const eventsCount = this.db.prepare('SELECT COUNT(*) as count FROM workflow_events').get() as { count: number };\n\n return {\n runs: runsCount.count,\n steps: stepsCount.count,\n events: eventsCount.count,\n };\n }\n}\n\n// ============================================================================\n// SQLite Row Types\n// ============================================================================\n//\n// `SQLiteRunRow`, `SQLiteStepRow`, and `SQLiteEventRow` now live in\n// `./sqlite-core.ts` alongside their pure row mappers.\n","/**\n * Shared core for the PostgreSQL storage adapters.\n *\n * `PostgresStorageAdapter` (the public, pool-owning adapter) and\n * `PostgresTransactionAdapter` (the in-transaction wrapper used by\n * `PostgresStorageAdapter.transaction()`) both implement the same CRUD subset\n * of `StorageAdapter`. Before this refactor they each carried their own copy\n * of every CRUD method, every row mapper, and every JSON-parse helper. The\n * only meaningful difference between them is which Kysely query builder they\n * target — an instance-bound one (`db.withSchema(schema)`) versus a\n * transaction-bound one (`trx.withSchema(schema)`).\n *\n * This module collapses that duplication:\n *\n * - Pure helpers (`safeJsonParse`, `safeParseField`, `safeParseOptionalField`,\n * row mappers, `applyRunsFilters`) live as standalone functions so callers\n * can use them directly without inheritance.\n *\n * - `PostgresStorageCore` is an abstract base class that implements the\n * CRUD subset of `StorageAdapter` against an abstract `qb` accessor and\n * an `ensureReady()` hook. Subclasses supply the schema-scoped query\n * builder (and an optional pre-call guard, e.g.\n * `PostgresStorageAdapter.ensureInitialized()`).\n *\n * No behavior change: every query and every JSON-parse path matches the\n * previous in-line implementations. The C2 transaction-pagination fix\n * (same-connection COUNT(*)) is preserved by the shared `listRuns`\n * implementation. The C1 migration error-propagation fix lives on\n * `PostgresStorageAdapter` and is unaffected by this module.\n */\n\nimport type { Kysely as KyselyType } from 'kysely';\nimport { generateId } from '../utils/id.js';\nimport { requirePostgresDeps } from '../utils/postgres-deps.js';\nimport { sanitizeErrorForStorage } from '../utils/logger.js';\nimport {\n safeParseField as sharedSafeParseField,\n safeParseOptionalField as sharedSafeParseOptionalField,\n type MapperContext,\n type SafeJsonParseContext,\n} from '../utils/safe-json.js';\nimport type {\n StorageAdapter,\n WorkflowRunRecord,\n WorkflowRunStepRecord,\n WorkflowEventRecord,\n ListRunsOptions,\n ListEventsOptions,\n PaginatedResult,\n CreateRunInput,\n UpdateRunInput,\n} from './types.js';\nimport type { Logger, RunStatus, StepStatus, WorkflowError } from '../core/types.js';\n\n/**\n * Re-export the shared `MapperContext` from `src/utils/safe-json.ts` so\n * existing imports of `MapperContext` from this module continue to work.\n */\nexport type { MapperContext };\n\n// ============================================================================\n// Database row types (re-declared here so postgres-core.ts is self-contained;\n// they mirror the table definitions in postgres.ts)\n// ============================================================================\n\n/** Kysely row type for the workflow_runs table. */\nexport interface WorkflowRunsTable {\n id: string;\n kind: string;\n status: string;\n parent_run_id: string | null;\n input_json: string;\n metadata_json: string;\n context_json: string;\n output_json: string | null;\n error_json: string | null;\n priority: number;\n timeout_ms: number | null;\n created_at: Date;\n started_at: Date | null;\n finished_at: Date | null;\n}\n\n/** Kysely row type for the workflow_run_steps table. */\nexport interface WorkflowRunStepsTable {\n id: string;\n run_id: string;\n step_key: string;\n step_name: string;\n status: string;\n attempt: number;\n result_json: string | null;\n error_json: string | null;\n started_at: Date | null;\n finished_at: Date | null;\n}\n\n/** Kysely row type for the workflow_events table. */\nexport interface WorkflowEventsTable {\n id: string;\n run_id: string;\n step_key: string | null;\n event_type: string;\n level: string;\n payload_json: string | null;\n timestamp: Date;\n}\n\n/** Combined Kysely schema covering the tables touched by the shared core. */\nexport interface PostgresCoreDatabase {\n runs: WorkflowRunsTable;\n workflow_run_steps: WorkflowRunStepsTable;\n workflow_events: WorkflowEventsTable;\n}\n\n/** Schema-scoped query builder produced by `db.withSchema(schemaName)`. */\nexport type SchemaQueryBuilder<DB extends PostgresCoreDatabase = PostgresCoreDatabase> =\n ReturnType<KyselyType<DB>['withSchema']>;\n\n/** Concrete core-only schema query builder used inside the shared base class. */\nexport type CoreSchemaQueryBuilder = SchemaQueryBuilder<PostgresCoreDatabase>;\n\n// ============================================================================\n// JSON-parse helpers (pure)\n// ============================================================================\n//\n// Storage adapters supply a `MapperContext` ({ component, logger }); each\n// helper extends it with the row id and column being parsed so a structured\n// log emitted on corruption identifies the bad row. The raw value is never\n// logged — see `src/utils/safe-json.ts`.\n\n/** Parse a column value if it's still a JSON string; pass through otherwise. */\nexport function safeParseField(\n value: unknown,\n fallback: unknown,\n ctx: SafeJsonParseContext,\n): unknown {\n return sharedSafeParseField(value, fallback, ctx);\n}\n\n/** Same as `safeParseField` but returns `undefined` for falsy/missing values. */\nexport function safeParseOptionalField(\n value: unknown,\n ctx: SafeJsonParseContext,\n): unknown {\n return sharedSafeParseOptionalField(value, ctx);\n}\n\n// ============================================================================\n// Row mappers (pure)\n// ============================================================================\n\n/** Map a `runs` row to the public `WorkflowRunRecord` shape. */\nexport function mapRunRow(row: WorkflowRunsTable, ctx: MapperContext): WorkflowRunRecord {\n const fieldCtx = (column: string): SafeJsonParseContext => ({ ...ctx, rowId: row.id, column });\n return {\n id: row.id,\n kind: row.kind,\n status: row.status as RunStatus,\n parentRunId: row.parent_run_id ?? undefined,\n input: safeParseField(row.input_json, {}, fieldCtx('input_json')) as Record<string, unknown>,\n context: safeParseField(row.context_json, {}, fieldCtx('context_json')) as Record<string, unknown>,\n output: safeParseOptionalField(row.output_json, fieldCtx('output_json')) as Record<string, unknown> | undefined,\n error: safeParseOptionalField(row.error_json, fieldCtx('error_json')) as WorkflowError | undefined,\n metadata: safeParseField(row.metadata_json, {}, fieldCtx('metadata_json')) as Record<string, unknown>,\n priority: row.priority ?? 0,\n timeoutMs: row.timeout_ms ?? undefined,\n createdAt: new Date(row.created_at),\n startedAt: row.started_at ? new Date(row.started_at) : undefined,\n finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,\n };\n}\n\n/** Map a `workflow_run_steps` row to the public `WorkflowRunStepRecord` shape. */\nexport function mapStepRow(row: WorkflowRunStepsTable, ctx: MapperContext): WorkflowRunStepRecord {\n const fieldCtx = (column: string): SafeJsonParseContext => ({ ...ctx, rowId: row.id, column });\n return {\n id: row.id,\n runId: row.run_id,\n stepKey: row.step_key,\n stepName: row.step_name,\n status: row.status as StepStatus,\n attempt: row.attempt,\n result: safeParseOptionalField(row.result_json, fieldCtx('result_json')),\n error: safeParseOptionalField(row.error_json, fieldCtx('error_json')) as WorkflowError | undefined,\n startedAt: row.started_at ? new Date(row.started_at) : undefined,\n finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,\n };\n}\n\n/** Map a `workflow_events` row to the public `WorkflowEventRecord` shape. */\nexport function mapEventRow(row: WorkflowEventsTable, ctx: MapperContext): WorkflowEventRecord {\n const fieldCtx: SafeJsonParseContext = { ...ctx, rowId: row.id, column: 'payload_json' };\n return {\n id: row.id,\n runId: row.run_id,\n stepKey: row.step_key ?? undefined,\n eventType: row.event_type,\n level: row.level as 'info' | 'warn' | 'error',\n payload: safeParseOptionalField(row.payload_json, fieldCtx),\n timestamp: new Date(row.timestamp),\n };\n}\n\n// ============================================================================\n// Filter helpers (pure)\n// ============================================================================\n\n/**\n * Apply the common `kind`/`status`/`parentRunId` filters from `ListRunsOptions`\n * to a Kysely select query. Used by both the data and count queries in\n * `listRuns` so they share exactly the same WHERE clause.\n */\nexport function applyRunsFilters<T extends { where(col: any, op: any, val: any): T }>(\n query: T,\n options: ListRunsOptions,\n): T {\n if (options.kind) {\n query = query.where('kind', '=', options.kind);\n }\n if (options.status) {\n const statuses = Array.isArray(options.status) ? options.status : [options.status];\n query = query.where('status', 'in', statuses);\n }\n if (options.parentRunId !== undefined) {\n query = query.where('parent_run_id', '=', options.parentRunId);\n }\n return query;\n}\n\n// ============================================================================\n// Shared base class\n// ============================================================================\n\n/**\n * Abstract base implementing the CRUD subset of `StorageAdapter` against a\n * schema-scoped Kysely query builder. Subclasses must provide the query\n * builder via `qb` and may override `ensureReady()` to gate calls (the\n * pool-owning adapter uses this to enforce `initialize()` having run).\n *\n * The base class deliberately does **not** own the connection pool, schema\n * migrations, or transaction control — those stay with `PostgresStorageAdapter`.\n *\n * The base class targets `PostgresCoreDatabase` (the three core tables) so\n * Kysely's row-type machinery can fully resolve column types on every query.\n * Subclasses with a wider schema (e.g. `PostgresStorageAdapter` with\n * `stepflow_step_results`) keep their own typed handle for the extra tables\n * and return a core-typed view here.\n */\nexport abstract class PostgresStorageCore implements StorageAdapter {\n protected constructor(\n protected readonly schema: string,\n protected readonly logger?: Logger,\n ) {}\n\n /** Schema-scoped query builder. Subclasses return the bound builder. */\n protected abstract get qb(): CoreSchemaQueryBuilder;\n\n /** Component label used in corruption warnings; defines the JSON-parse `MapperContext`. */\n protected abstract get warnLabel(): string;\n\n /** Mapper context handed to row mappers — pairs the component label with the optional logger. */\n protected get mapperCtx(): MapperContext {\n return { component: this.warnLabel, logger: this.logger };\n }\n\n /**\n * Hook called at the start of every CRUD method. The pool-owning adapter\n * overrides this to throw before `initialize()` has run; the transaction\n * wrapper leaves it as a no-op.\n */\n protected ensureReady(): void {}\n\n // --------------------------------------------------------------------------\n // Run operations\n // --------------------------------------------------------------------------\n\n async createRun(\n run: CreateRunInput | Omit<WorkflowRunRecord, 'id' | 'createdAt'>,\n ): Promise<WorkflowRunRecord> {\n this.ensureReady();\n const id = 'id' in run && run.id ? run.id : generateId();\n const createdAt = new Date();\n\n await this.qb\n .insertInto('runs')\n .values({\n id,\n kind: run.kind,\n status: run.status,\n parent_run_id: 'parentRunId' in run ? (run.parentRunId ?? null) : null,\n input_json: JSON.stringify(run.input),\n metadata_json: JSON.stringify(run.metadata ?? {}),\n context_json: JSON.stringify(run.context ?? {}),\n output_json: null,\n error_json:\n 'error' in run && run.error\n ? JSON.stringify(sanitizeErrorForStorage(run.error))\n : null,\n priority: 'priority' in run ? (run.priority ?? 0) : 0,\n timeout_ms: 'timeoutMs' in run ? (run.timeoutMs ?? null) : null,\n created_at: createdAt,\n started_at: null,\n finished_at: null,\n })\n .execute();\n\n return {\n id,\n kind: run.kind,\n status: run.status as RunStatus,\n parentRunId: 'parentRunId' in run ? run.parentRunId : undefined,\n input: run.input,\n context: run.context ?? {},\n output: undefined,\n error: undefined,\n metadata: run.metadata ?? {},\n priority: 'priority' in run ? (run.priority ?? 0) : 0,\n timeoutMs: 'timeoutMs' in run ? run.timeoutMs : undefined,\n createdAt,\n };\n }\n\n async getRun(runId: string): Promise<WorkflowRunRecord | null> {\n this.ensureReady();\n const row = await this.qb\n .selectFrom('runs')\n .selectAll()\n .where('id', '=', runId)\n .executeTakeFirst();\n\n return row ? mapRunRow(row as WorkflowRunsTable, this.mapperCtx) : null;\n }\n\n async updateRun(\n runId: string,\n updates: UpdateRunInput | Partial<WorkflowRunRecord>,\n ): Promise<void> {\n this.ensureReady();\n const updateData: Partial<WorkflowRunsTable> = {};\n\n if (updates.status !== undefined) {\n updateData.status = updates.status;\n }\n if (updates.context !== undefined) {\n updateData.context_json = JSON.stringify(updates.context);\n }\n if ('output' in updates && updates.output !== undefined) {\n updateData.output_json = JSON.stringify(updates.output);\n }\n if (updates.error !== undefined) {\n updateData.error_json = JSON.stringify(\n sanitizeErrorForStorage(updates.error as WorkflowError),\n );\n }\n if (updates.startedAt !== undefined) {\n updateData.started_at = updates.startedAt;\n }\n if (updates.finishedAt !== undefined) {\n updateData.finished_at = updates.finishedAt;\n }\n\n if (Object.keys(updateData).length > 0) {\n await this.qb\n .updateTable('runs')\n .set(updateData)\n .where('id', '=', runId)\n .execute();\n }\n }\n\n async listRuns(\n options: ListRunsOptions = {},\n ): Promise<PaginatedResult<WorkflowRunRecord>> {\n this.ensureReady();\n const { sql } = requirePostgresDeps();\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n\n // Same-connection COUNT(*) so totals reflect rows visible to this builder\n // (preserves the C2 transaction-pagination fix from PR #47).\n const countQuery = applyRunsFilters(\n this.qb.selectFrom('runs').select(sql<number>`count(*)`.as('count')),\n options,\n );\n const dataQuery = applyRunsFilters(this.qb.selectFrom('runs').selectAll(), options);\n\n const orderBy = options.orderBy ?? 'createdAt';\n const orderDirection = options.orderDirection ?? 'desc';\n const orderColumn =\n orderBy === 'createdAt'\n ? 'created_at'\n : orderBy === 'startedAt'\n ? 'started_at'\n : 'finished_at';\n\n const countResult = (await countQuery.executeTakeFirst()) as\n | { count?: string | number }\n | undefined;\n const total = Number(countResult?.count ?? 0);\n\n const rows = await dataQuery\n .orderBy(orderColumn, orderDirection)\n .limit(limit)\n .offset(offset)\n .execute();\n\n return {\n items: rows.map((row) => mapRunRow(row as WorkflowRunsTable, this.mapperCtx)),\n total,\n };\n }\n\n // --------------------------------------------------------------------------\n // Step operations\n // --------------------------------------------------------------------------\n\n async createStep(\n step: Omit<WorkflowRunStepRecord, 'id'>,\n ): Promise<WorkflowRunStepRecord> {\n this.ensureReady();\n const id = generateId();\n\n await this.qb\n .insertInto('workflow_run_steps')\n .values({\n id,\n run_id: step.runId,\n step_key: step.stepKey,\n step_name: step.stepName,\n status: step.status,\n attempt: step.attempt,\n result_json: step.result !== undefined ? JSON.stringify(step.result) : null,\n error_json: step.error\n ? JSON.stringify(sanitizeErrorForStorage(step.error))\n : null,\n started_at: step.startedAt ?? null,\n finished_at: step.finishedAt ?? null,\n })\n .execute();\n\n return { ...step, id };\n }\n\n async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {\n this.ensureReady();\n const row = await this.qb\n .selectFrom('workflow_run_steps')\n .selectAll()\n .where('id', '=', stepId)\n .executeTakeFirst();\n\n return row ? mapStepRow(row as WorkflowRunStepsTable, this.mapperCtx) : null;\n }\n\n async updateStep(\n stepId: string,\n updates: Partial<WorkflowRunStepRecord>,\n ): Promise<void> {\n this.ensureReady();\n const updateData: Partial<WorkflowRunStepsTable> = {};\n\n if (updates.status !== undefined) {\n updateData.status = updates.status;\n }\n if (updates.attempt !== undefined) {\n updateData.attempt = updates.attempt;\n }\n if (updates.result !== undefined) {\n updateData.result_json = JSON.stringify(updates.result);\n }\n if (updates.error !== undefined) {\n updateData.error_json = JSON.stringify(\n sanitizeErrorForStorage(updates.error as WorkflowError),\n );\n }\n if (updates.finishedAt !== undefined) {\n updateData.finished_at = updates.finishedAt;\n }\n\n if (Object.keys(updateData).length > 0) {\n await this.qb\n .updateTable('workflow_run_steps')\n .set(updateData)\n .where('id', '=', stepId)\n .execute();\n }\n }\n\n async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {\n this.ensureReady();\n const rows = await this.qb\n .selectFrom('workflow_run_steps')\n .selectAll()\n .where('run_id', '=', runId)\n .orderBy('started_at', 'asc')\n .execute();\n\n return rows.map((row) => mapStepRow(row as WorkflowRunStepsTable, this.mapperCtx));\n }\n\n // --------------------------------------------------------------------------\n // Event operations\n // --------------------------------------------------------------------------\n\n async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {\n this.ensureReady();\n const id = generateId();\n\n await this.qb\n .insertInto('workflow_events')\n .values({\n id,\n run_id: event.runId,\n step_key: event.stepKey ?? null,\n event_type: event.eventType,\n level: event.level,\n payload_json: event.payload !== undefined ? JSON.stringify(event.payload) : null,\n timestamp: event.timestamp,\n })\n .execute();\n }\n\n async getEventsForRun(\n runId: string,\n options: ListEventsOptions = {},\n ): Promise<WorkflowEventRecord[]> {\n this.ensureReady();\n const limit = options.limit ?? 1000;\n const offset = options.offset ?? 0;\n\n let query = this.qb\n .selectFrom('workflow_events')\n .selectAll()\n .where('run_id', '=', runId);\n\n if (options.stepKey) {\n query = query.where('step_key', '=', options.stepKey);\n }\n if (options.level) {\n query = query.where('level', '=', options.level);\n }\n\n query = query.orderBy('timestamp', 'asc').limit(limit).offset(offset);\n\n const rows = await query.execute();\n\n return rows.map((row) => mapEventRow(row as WorkflowEventsTable, this.mapperCtx));\n }\n}\n","/**\n * PostgreSQL storage adapter using Kysely.\n *\n * Provides durable persistence for workflow runs, steps, and events with\n * support for distributed deployments and connection pooling.\n *\n * ## Module layout\n *\n * - `PostgresStorageAdapter` (this file) owns the `pg.Pool` / Kysely\n * instance, runs schema migrations on `initialize()`, and implements all\n * non-CRUD methods: connection lifecycle (`close`, `transaction`),\n * workflow-level operations (`dequeueRun`, `cleanupStaleRuns`,\n * `markRunsAsFailed`, `getStats`, `deleteRun`, `deleteOldRuns`,\n * `getInterruptedRuns`, `getLastCompletedStep`), and the\n * `stepflow_step_results` accessors.\n *\n * - `PostgresTransactionAdapter` (this file) is a thin shell used by\n * `PostgresStorageAdapter.transaction()`. It binds a transaction-scoped\n * Kysely query builder; everything else comes from the shared core.\n * Exported only for regression tests.\n *\n * - `PostgresStorageCore` (./postgres-core.ts) holds the shared CRUD\n * methods (`createRun`, `getRun`, `updateRun`, `listRuns`, `createStep`,\n * `getStep`, `updateStep`, `getStepsForRun`, `saveEvent`,\n * `getEventsForRun`), the row mappers, and the JSON-parse helpers. Both\n * classes here extend it.\n */\n\nimport type { Kysely as KyselyType } from 'kysely';\nimport type { Pool, PoolConfig } from 'pg';\nimport type { Logger } from '../core/types.js';\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 { generateId } from '../utils/id.js';\nimport type {\n StorageAdapter,\n WorkflowRunRecord,\n WorkflowRunStepRecord,\n StepResult,\n ExtendedStepStatus,\n} from './types.js';\nimport {\n PostgresStorageCore,\n mapRunRow,\n mapStepRow,\n safeParseOptionalField,\n type CoreSchemaQueryBuilder,\n type PostgresCoreDatabase,\n type SchemaQueryBuilder,\n type WorkflowRunsTable,\n type WorkflowRunStepsTable,\n} from './postgres-core.js';\n\n/** Strip `stack` from a generic error record before persistence. */\nfunction stripStack<T extends Record<string, unknown>>(error: T): Omit<T, 'stack'> {\n const { stack: _stack, ...rest } = error;\n return rest as Omit<T, 'stack'>;\n}\n\n// ============================================================================\n// Database Types (Kysely schema)\n// ============================================================================\n//\n// The `runs`, `workflow_run_steps`, and `workflow_events` table shapes live\n// in `./postgres-core.ts` so the shared CRUD core can target them without\n// re-declaration. The full `StepflowDatabase` schema below adds the\n// adapter-only `stepflow_step_results` table (extended storage).\n\n/** Kysely row type for the stepflow_step_results table (extended storage). */\ninterface StepflowStepResultsTable {\n id: string;\n /** Foreign key to runs.id. */\n run_id: string;\n /** Step name identifier. */\n step_name: string;\n /** Current step status (maps to ExtendedStepStatus). */\n status: string;\n /** JSON-serialized step output, null until step completes. */\n output_json: string | null;\n /** JSON-serialized error object, null unless step failed. */\n error_json: string | null;\n /** Current retry attempt number (1-based). */\n attempt: number;\n started_at: Date | null;\n completed_at: Date | null;\n}\n\ninterface StepflowDatabase extends PostgresCoreDatabase {\n stepflow_step_results: StepflowStepResultsTable;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Configuration options for the PostgreSQL storage adapter.\n */\nexport interface PostgresStorageConfig {\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 Stepflow tables.\n * @default 'public'\n */\n schema?: string;\n\n /**\n * Automatically create tables on initialize().\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// PostgreSQL Storage Adapter\n// ============================================================================\n\n/**\n * PostgreSQL implementation of StorageAdapter using Kysely.\n *\n * Features:\n * - Connection pooling (shared or dedicated)\n * - Automatic table creation\n * - Transaction support\n * - Atomic dequeue operations for distributed workers\n * - JSONB storage for flexible data\n *\n * @example\n * ```typescript\n * import { PostgresStorageAdapter } from 'stepflow/storage';\n *\n * const storage = new PostgresStorageAdapter({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * await storage.initialize();\n *\n * const engine = new WorkflowEngine({ storage });\n * ```\n *\n * @example Sharing connection pool\n * ```typescript\n * import pg from 'pg';\n * import { PostgresStorageAdapter } from 'stepflow/storage';\n *\n * // Application's existing pool\n * const pool = new pg.Pool({\n * connectionString: process.env.DATABASE_URL,\n * max: 20,\n * });\n *\n * // Share with Stepflow\n * const storage = new PostgresStorageAdapter({ pool });\n * ```\n */\nexport class PostgresStorageAdapter extends PostgresStorageCore {\n private db!: KyselyType<StepflowDatabase>;\n private pool!: Pool;\n private ownsPool = false;\n private autoMigrate: boolean;\n private initialized = false;\n private config: PostgresStorageConfig;\n\n constructor(config: PostgresStorageConfig) {\n const schema = config.schema ?? 'public';\n if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(schema)) {\n throw new Error(\n `Invalid schema name \"${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 super(schema, config.logger);\n this.autoMigrate = config.autoMigrate !== false;\n this.config = config;\n }\n\n /**\n * Core-tables query builder consumed by `PostgresStorageCore`. Returns the\n * schema-scoped Kysely view typed against the three core tables, which is\n * what the shared CRUD methods need.\n */\n protected override get qb(): CoreSchemaQueryBuilder {\n return this.db.withSchema(this.schema) as unknown as CoreSchemaQueryBuilder;\n }\n\n /**\n * Extended query builder for adapter-only tables (currently\n * `stepflow_step_results`). Kept separate from `qb` so the base class stays\n * typed against the core schema.\n */\n private get extQb(): SchemaQueryBuilder<StepflowDatabase> {\n return this.db.withSchema(this.schema);\n }\n\n /** Tag used by shared row mappers in `console.warn` messages. */\n protected override get warnLabel(): string {\n return 'PostgresStorageAdapter';\n }\n\n /** Override of `PostgresStorageCore.ensureReady()` — enforces `initialize()`. */\n protected override ensureReady(): void {\n this.ensureInitialized();\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n throw new Error(\n 'PostgresStorageAdapter is not initialized. Call initialize() before using the adapter.'\n );\n }\n }\n\n /**\n * Initialize the storage adapter.\n * Creates tables 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 'PostgresStorageConfig 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 // Kysely's destroy() already calls pool.end() via PostgresDialect,\n // so we must NOT call pool.end() again.\n }\n\n // ============================================================================\n // Schema Creation\n // ============================================================================\n\n /**\n * Run a single migration step. Tolerates only known-benign Postgres errors\n * (duplicate object codes that map to \"already applied\"), and rethrows\n * everything else with a clear message so partial-state upgrades are not\n * silently swallowed.\n */\n protected async runMigration(label: string, fn: () => Promise<unknown>): Promise<void> {\n try {\n await fn();\n } catch (err) {\n if (PostgresStorageAdapter.isBenignMigrationError(err)) {\n return;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Schema migration failed (${label}): ${message}`, { cause: err });\n }\n }\n\n /**\n * Postgres SQLSTATE codes that indicate a migration step has already been\n * applied (object already exists). These are safe to ignore on idempotent\n * re-runs; everything else must propagate.\n *\n * - 42701: duplicate_column\n * - 42P07: duplicate_table\n * - 42P06: duplicate_schema\n * - 42710: duplicate_object (e.g., index, constraint)\n */\n private static readonly BENIGN_MIGRATION_CODES = new Set([\n '42701',\n '42P07',\n '42P06',\n '42710',\n ]);\n\n private static isBenignMigrationError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const code = (err as { code?: unknown }).code;\n return typeof code === 'string' && PostgresStorageAdapter.BENIGN_MIGRATION_CODES.has(code);\n }\n\n /**\n * Create the workflow tables if they don't exist.\n */\n private async createTables(): Promise<void> {\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 runs table with new columns\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql.table(`${this.schema}.runs`)} (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n status TEXT NOT NULL,\n parent_run_id TEXT,\n input_json JSONB NOT NULL DEFAULT '{}',\n metadata_json JSONB NOT NULL DEFAULT '{}',\n context_json JSONB NOT NULL DEFAULT '{}',\n output_json JSONB,\n error_json JSONB,\n priority INTEGER NOT NULL DEFAULT 0,\n timeout_ms INTEGER,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n started_at TIMESTAMPTZ,\n finished_at TIMESTAMPTZ,\n CONSTRAINT runs_status_check CHECK (\n status IN ('pending', 'queued', 'running', 'succeeded', 'failed', 'canceled', 'timeout')\n )\n )\n `.execute(this.db);\n\n // Add new columns if they don't exist (for migrations)\n await this.runMigration(\n `add columns to ${this.schema}.runs`,\n () => sql`\n ALTER TABLE ${sql.table(`${this.schema}.runs`)}\n ADD COLUMN IF NOT EXISTS output_json JSONB,\n ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS timeout_ms INTEGER\n `.execute(this.db)\n );\n\n // Create indexes for runs\n await sql`\n CREATE INDEX IF NOT EXISTS idx_runs_kind_status\n ON ${sql.table(`${this.schema}.runs`)} (kind, status)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_runs_parent\n ON ${sql.table(`${this.schema}.runs`)} (parent_run_id)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_runs_created\n ON ${sql.table(`${this.schema}.runs`)} (created_at DESC)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_runs_status\n ON ${sql.table(`${this.schema}.runs`)} (status)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_runs_priority\n ON ${sql.table(`${this.schema}.runs`)} (priority DESC, created_at ASC)\n `.execute(this.db);\n\n // Create workflow_run_steps table (legacy)\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql.table(`${this.schema}.workflow_run_steps`)} (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES ${sql.table(`${this.schema}.runs`)} (id) ON DELETE CASCADE,\n step_key TEXT NOT NULL,\n step_name TEXT,\n status TEXT NOT NULL,\n attempt INTEGER NOT NULL DEFAULT 1,\n result_json JSONB,\n error_json JSONB,\n started_at TIMESTAMPTZ,\n finished_at TIMESTAMPTZ,\n CONSTRAINT workflow_run_steps_status_check CHECK (\n status IN ('pending', 'running', 'succeeded', 'failed', 'skipped', 'completed')\n )\n )\n `.execute(this.db);\n\n // Create indexes for workflow_run_steps\n await sql`\n CREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run\n ON ${sql.table(`${this.schema}.workflow_run_steps`)} (run_id)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run_key\n ON ${sql.table(`${this.schema}.workflow_run_steps`)} (run_id, step_key)\n `.execute(this.db);\n\n // Create stepflow_step_results table (new)\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql.table(`${this.schema}.stepflow_step_results`)} (\n id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,\n run_id TEXT NOT NULL REFERENCES ${sql.table(`${this.schema}.runs`)} (id) ON DELETE CASCADE,\n step_name TEXT NOT NULL,\n status TEXT NOT NULL,\n output_json JSONB,\n error_json JSONB,\n attempt INTEGER NOT NULL DEFAULT 1,\n started_at TIMESTAMPTZ,\n completed_at TIMESTAMPTZ,\n CONSTRAINT stepflow_step_results_status_check CHECK (\n status IN ('pending', 'running', 'completed', 'failed', 'skipped')\n ),\n UNIQUE (run_id, step_name)\n )\n `.execute(this.db);\n\n // Create indexes for stepflow_step_results\n await sql`\n CREATE INDEX IF NOT EXISTS idx_stepflow_step_results_run\n ON ${sql.table(`${this.schema}.stepflow_step_results`)} (run_id)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_stepflow_step_results_run_name\n ON ${sql.table(`${this.schema}.stepflow_step_results`)} (run_id, step_name)\n `.execute(this.db);\n\n // Create workflow_events table\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql.table(`${this.schema}.workflow_events`)} (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES ${sql.table(`${this.schema}.runs`)} (id) ON DELETE CASCADE,\n step_key TEXT,\n event_type TEXT NOT NULL,\n level TEXT NOT NULL,\n payload_json JSONB,\n timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n CONSTRAINT workflow_events_level_check CHECK (\n level IN ('info', 'warn', 'error')\n )\n )\n `.execute(this.db);\n\n // Create indexes for workflow_events\n await sql`\n CREATE INDEX IF NOT EXISTS idx_workflow_events_run\n ON ${sql.table(`${this.schema}.workflow_events`)} (run_id)\n `.execute(this.db);\n\n await sql`\n CREATE INDEX IF NOT EXISTS idx_workflow_events_run_ts\n ON ${sql.table(`${this.schema}.workflow_events`)} (run_id, timestamp)\n `.execute(this.db);\n }\n\n // ============================================================================\n // Run / Step / Event CRUD\n // ============================================================================\n //\n // The CRUD methods (`createRun`, `getRun`, `updateRun`, `listRuns`,\n // `createStep`, `getStep`, `updateStep`, `getStepsForRun`, `saveEvent`,\n // `getEventsForRun`) are inherited from `PostgresStorageCore`. Each call\n // routes through `this.qb` (overridden above) and `this.ensureReady()`\n // which calls `this.ensureInitialized()`.\n\n // ============================================================================\n // Transaction Support\n // ============================================================================\n\n /**\n * Execute a function within a database transaction.\n */\n async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {\n this.ensureInitialized();\n return await this.db.transaction().execute(async (trx) => {\n // Create a transactional adapter wrapper\n const txAdapter = new PostgresTransactionAdapter(trx, this.schema, this.logger);\n return await fn(txAdapter);\n });\n }\n\n // ============================================================================\n // Cleanup Operations\n // ============================================================================\n\n /**\n * Delete runs older than the specified date.\n * Also deletes associated steps and events (via CASCADE).\n */\n async deleteOldRuns(olderThan: Date): Promise<number> {\n this.ensureInitialized();\n const result = await this.qb\n .deleteFrom('runs')\n .where('created_at', '<', olderThan)\n .executeTakeFirst();\n\n return Number(result.numDeletedRows);\n }\n\n // ============================================================================\n // Resume Support\n // ============================================================================\n\n /**\n * Get all runs that were interrupted (status is 'running' or 'queued').\n * These runs can potentially be resumed.\n */\n async getInterruptedRuns(): Promise<WorkflowRunRecord[]> {\n this.ensureInitialized();\n const rows = await this.qb\n .selectFrom('runs')\n .selectAll()\n .where('status', 'in', ['queued', 'running'])\n .orderBy('created_at', 'asc')\n .execute();\n\n return rows.map(row => mapRunRow(row as WorkflowRunsTable, this.mapperCtx));\n }\n\n /**\n * Get the last completed step for a run.\n * Useful for resuming from a checkpoint.\n */\n async getLastCompletedStep(runId: string): Promise<WorkflowRunStepRecord | null> {\n this.ensureInitialized();\n const row = await this.qb\n .selectFrom('workflow_run_steps')\n .selectAll()\n .where('run_id', '=', runId)\n .where('status', '=', 'succeeded')\n .orderBy('finished_at', 'desc')\n .limit(1)\n .executeTakeFirst();\n\n return row ? mapStepRow(row as WorkflowRunStepsTable, this.mapperCtx) : null;\n }\n\n // ============================================================================\n // Atomic Dequeue (for distributed workers)\n // ============================================================================\n\n /**\n * Atomically dequeue a run for processing.\n * Uses FOR UPDATE SKIP LOCKED for safe concurrent access.\n *\n * @param workflowKinds - Optional list of workflow kinds to filter by\n * @returns The dequeued run, or null if no runs are available\n */\n async dequeueRun(workflowKinds?: string[]): Promise<WorkflowRunRecord | null> {\n this.ensureInitialized();\n // Use raw SQL for FOR UPDATE SKIP LOCKED\n const kindFilter = workflowKinds && workflowKinds.length > 0\n ? sql`AND kind = ANY(${workflowKinds}::text[])`\n : sql``;\n\n const result = await sql<WorkflowRunsTable>`\n UPDATE ${sql.table(`${this.schema}.runs`)}\n SET status = 'running', started_at = NOW()\n WHERE id = (\n SELECT id FROM ${sql.table(`${this.schema}.runs`)}\n WHERE status = 'queued' ${kindFilter}\n ORDER BY priority DESC, created_at ASC\n LIMIT 1\n FOR UPDATE SKIP LOCKED\n )\n RETURNING *\n `.execute(this.db);\n\n const row = result.rows[0];\n return row ? mapRunRow(row, this.mapperCtx) : null;\n }\n\n // ============================================================================\n // Row Mapping (adapter-only tables)\n // ============================================================================\n //\n // Mappers and JSON-parse helpers for the three core tables live in\n // `./postgres-core.ts`. The single mapper kept here covers\n // `stepflow_step_results`, the only table outside the core schema.\n\n private mapStepResultRow(row: StepflowStepResultsTable): StepResult {\n const ctx = this.mapperCtx;\n return {\n id: row.id,\n runId: row.run_id,\n stepName: row.step_name,\n status: row.status as ExtendedStepStatus,\n output: safeParseOptionalField(row.output_json, {\n ...ctx,\n rowId: row.id,\n column: 'output_json',\n }) as Record<string, unknown> | undefined,\n error: safeParseOptionalField(row.error_json, {\n ...ctx,\n rowId: row.id,\n column: 'error_json',\n }) as Record<string, unknown> | undefined,\n attempt: row.attempt,\n startedAt: row.started_at ? new Date(row.started_at) : undefined,\n completedAt: row.completed_at ? new Date(row.completed_at) : undefined,\n };\n }\n\n // ============================================================================\n // New WorkflowStorage Methods\n // ============================================================================\n\n /**\n * Delete a workflow run by ID.\n * Also deletes associated steps and events (via CASCADE).\n */\n async deleteRun(id: string): Promise<void> {\n this.ensureInitialized();\n await this.qb\n .deleteFrom('runs')\n .where('id', '=', id)\n .execute();\n }\n\n /**\n * Cleanup stale runs that have exceeded their timeout.\n * Marks them as 'timeout' status with an appropriate error.\n *\n * @param defaultTimeoutMs - Default timeout in ms for runs without explicit timeout (default: 600000 = 10 minutes)\n * @returns Number of runs marked as timed out\n */\n async cleanupStaleRuns(defaultTimeoutMs: number = 600000): Promise<number> {\n this.ensureInitialized();\n const result = await sql<{ id: string }>`\n UPDATE ${sql.table(`${this.schema}.runs`)}\n SET\n status = 'timeout',\n error_json = jsonb_build_object(\n 'code', 'WORKFLOW_TIMEOUT',\n 'message', 'Workflow exceeded maximum execution time and was marked as timed out'\n ),\n finished_at = NOW()\n WHERE status = 'running'\n AND started_at IS NOT NULL\n AND (\n (timeout_ms IS NOT NULL AND started_at < NOW() - (timeout_ms || ' milliseconds')::interval)\n OR\n (timeout_ms IS NULL AND started_at < NOW() - (${defaultTimeoutMs} || ' milliseconds')::interval)\n )\n RETURNING id\n `.execute(this.db);\n\n return result.rows.length;\n }\n\n /**\n * Mark multiple runs as failed with a given reason.\n * Useful for cleanup when a worker shuts down unexpectedly.\n *\n * @param runIds - Array of run IDs to mark as failed\n * @param reason - Reason message for the failure\n */\n async markRunsAsFailed(runIds: string[], reason: string): Promise<void> {\n this.ensureInitialized();\n if (runIds.length === 0) return;\n\n await sql`\n UPDATE ${sql.table(`${this.schema}.runs`)}\n SET\n status = 'failed',\n error_json = jsonb_build_object(\n 'code', 'WORKER_SHUTDOWN',\n 'message', ${reason}\n ),\n finished_at = NOW()\n WHERE id = ANY(${runIds})\n AND status = 'running'\n `.execute(this.db);\n }\n\n /**\n * Get a specific step result by run ID and step name.\n */\n async getStepResult(runId: string, stepName: string): Promise<StepResult | undefined> {\n this.ensureInitialized();\n const row = await this.extQb\n .selectFrom('stepflow_step_results')\n .selectAll()\n .where('run_id', '=', runId)\n .where('step_name', '=', stepName)\n .executeTakeFirst();\n\n return row ? this.mapStepResultRow(row) : undefined;\n }\n\n /**\n * Get all step results for a run.\n */\n async getStepResults(runId: string): Promise<StepResult[]> {\n this.ensureInitialized();\n const rows = await this.extQb\n .selectFrom('stepflow_step_results')\n .selectAll()\n .where('run_id', '=', runId)\n .orderBy('started_at', 'asc')\n .execute();\n\n return rows.map(row => this.mapStepResultRow(row));\n }\n\n /**\n * Save or update a step result.\n * Uses upsert to handle both new and existing results.\n */\n async saveStepResult(result: Omit<StepResult, 'id'> & { id?: string }): Promise<void> {\n this.ensureInitialized();\n const id = result.id ?? generateId();\n\n await this.extQb\n .insertInto('stepflow_step_results')\n .values({\n id,\n run_id: result.runId,\n step_name: result.stepName,\n status: result.status,\n output_json: result.output ? JSON.stringify(result.output) : null,\n error_json: result.error ? JSON.stringify(stripStack(result.error)) : null,\n attempt: result.attempt,\n started_at: result.startedAt ?? null,\n completed_at: result.completedAt ?? null,\n })\n .onConflict(oc =>\n oc.columns(['run_id', 'step_name']).doUpdateSet({\n status: result.status,\n output_json: result.output ? JSON.stringify(result.output) : null,\n error_json: result.error ? JSON.stringify(stripStack(result.error)) : null,\n attempt: result.attempt,\n started_at: result.startedAt ?? null,\n completed_at: result.completedAt ?? null,\n })\n )\n .execute();\n }\n\n // ============================================================================\n // Utility Methods\n // ============================================================================\n\n /**\n * Get database statistics.\n */\n async getStats(): Promise<{ runs: number; steps: number; events: number }> {\n this.ensureInitialized();\n const [runsCount, stepsCount, eventsCount] = await Promise.all([\n this.qb.selectFrom('runs').select(sql`count(*)`.as('count')).executeTakeFirst(),\n this.qb.selectFrom('workflow_run_steps').select(sql`count(*)`.as('count')).executeTakeFirst(),\n this.qb.selectFrom('workflow_events').select(sql`count(*)`.as('count')).executeTakeFirst(),\n ]) as { count?: string | number }[];\n\n return {\n runs: Number(runsCount?.count ?? 0),\n steps: Number(stepsCount?.count ?? 0),\n events: Number(eventsCount?.count ?? 0),\n };\n }\n}\n\n// ============================================================================\n// Transaction Adapter (wraps Kysely transaction)\n// ============================================================================\n\n/**\n * A StorageAdapter wrapper for use within transactions.\n *\n * Used internally by `PostgresStorageAdapter.transaction()`. All CRUD methods\n * are inherited from `PostgresStorageCore`; this class only binds the\n * transaction-scoped query builder.\n *\n * Exported for regression testing; not part of the public API.\n */\nexport class PostgresTransactionAdapter extends PostgresStorageCore {\n private readonly _qb: CoreSchemaQueryBuilder;\n\n constructor(trx: KyselyType<StepflowDatabase>, schema: string, logger?: Logger) {\n super(schema, logger);\n this._qb = trx.withSchema(schema) as unknown as CoreSchemaQueryBuilder;\n }\n\n protected override get qb(): CoreSchemaQueryBuilder {\n return this._qb;\n }\n\n protected override get warnLabel(): string {\n return 'PostgresTransactionAdapter';\n }\n}\n\n// Convenience aliases for shorter naming\nexport { PostgresStorageAdapter as PostgresStorage };\nexport type { PostgresStorageConfig as PostgresStorageOptions };\n"],"mappings":"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","names":["safeParseField","safeParseOptionalField","mapRunRow","mapStepRow","mapEventRow","sql","mapRunRow","mapStepRow","safeParseOptionalField"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/events/memory.ts","../src/events/socketio.ts","../src/events/webhook.ts","../src/utils/pinned-fetch.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 */\n emit(event: WorkflowEvent): void {\n // Emit to global subscribers\n this.emitter.emit('event', event);\n\n // Emit to run-specific subscribers\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\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 if (body.length > this.maxPayloadBytes) {\n throw new Error(\n `Webhook payload exceeds maximum size (${body.length} 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 const response = this.hasCustomFetch || !pinned\n ? await this.fetchFn(endpoint.url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n })\n : await pinnedFetch(\n endpoint.url,\n { method: 'POST', headers, body, signal: controller.signal },\n pinned.address,\n pinned.family,\n );\n\n clearTimeout(timeoutId);\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 * Returns `null` for raw-IP URLs (already validated by `validateWebhookUrl`).\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 } | null> {\n const parsed = new URL(url);\n const hostname = parsed.hostname.toLowerCase();\n\n // Skip DNS resolution for raw IP literals — already checked by isBlockedHost in validateWebhookUrl.\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 null;\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 const ac = new AbortController();\n const timeoutId = setTimeout(() => ac.abort(), WebhookEventTransport.DNS_TIMEOUT_MS);\n\n let address: string;\n let family: 4 | 6;\n try {\n const result = await dns.lookup(hostname, { signal: ac.signal } as any);\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 if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`DNS lookup for webhook URL hostname \"${hostname}\" timed out`);\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 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 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: parsed.hostname,\n port,\n path: `${parsed.pathname || '/'}${parsed.search}`,\n method: init.method ?? 'GET',\n headers: init.headers,\n lookup,\n servername: parsed.hostname,\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.signal) {\n if (init.signal.aborted) {\n req.destroy(new Error('Request aborted'));\n } else {\n init.signal.addEventListener(\n 'abort',\n () => req.destroy(new Error('Request aborted')),\n { once: true },\n );\n }\n }\n\n if (init.body !== undefined) {\n req.write(init.body);\n }\n req.end();\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# GitHub Packages Setup for @multiplier-labs/stepflow
|
|
2
|
-
|
|
3
|
-
This document describes how to publish stepflow to GitHub Packages and how to install it in your projects.
|
|
4
|
-
|
|
5
|
-
## Publishing (Maintainers)
|
|
6
|
-
|
|
7
|
-
### Automated Publishing via Release
|
|
8
|
-
|
|
9
|
-
1. Ensure your changes are merged to the main branch
|
|
10
|
-
2. Go to **Releases** in the GitHub repository
|
|
11
|
-
3. Click **Draft a new release**
|
|
12
|
-
4. Create a tag matching the version in `package.json` (e.g., `v0.1.0`)
|
|
13
|
-
5. Publish the release
|
|
14
|
-
|
|
15
|
-
The GitHub Actions workflow (`.github/workflows/publish.yml`) automatically:
|
|
16
|
-
- Installs dependencies
|
|
17
|
-
- Builds the package
|
|
18
|
-
- Publishes to GitHub Packages
|
|
19
|
-
|
|
20
|
-
### Manual Publishing (if needed)
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# Authenticate with GitHub Packages
|
|
24
|
-
npm login --registry=https://npm.pkg.github.com
|
|
25
|
-
# Username: your GitHub username
|
|
26
|
-
# Password: Personal Access Token with write:packages scope
|
|
27
|
-
|
|
28
|
-
# Build and publish
|
|
29
|
-
npm run build
|
|
30
|
-
npm publish
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Installing in Projects
|
|
34
|
-
|
|
35
|
-
### Step 1: Configure npm for the @multiplier-labs scope
|
|
36
|
-
|
|
37
|
-
Create or update `.npmrc` in your project root:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
@multiplier-labs:registry=https://npm.pkg.github.com
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Step 2: Authenticate with GitHub Packages
|
|
44
|
-
|
|
45
|
-
Generate a Personal Access Token (PAT):
|
|
46
|
-
1. Go to GitHub → Settings → Developer settings → Personal access tokens
|
|
47
|
-
2. Generate a token with `read:packages` scope
|
|
48
|
-
3. Authenticate npm:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm login --registry=https://npm.pkg.github.com
|
|
52
|
-
# Username: your GitHub username
|
|
53
|
-
# Password: your PAT
|
|
54
|
-
# Email: your email
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Alternatively, add to your `~/.npmrc` (for global auth):
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
//npm.pkg.github.com/:_authToken=YOUR_PAT_HERE
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Step 3: Install the package
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npm install @multiplier-labs/stepflow
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Usage in CI/CD
|
|
70
|
-
|
|
71
|
-
For GitHub Actions, use the built-in `GITHUB_TOKEN`:
|
|
72
|
-
|
|
73
|
-
```yaml
|
|
74
|
-
- uses: actions/setup-node@v4
|
|
75
|
-
with:
|
|
76
|
-
node-version: '20'
|
|
77
|
-
registry-url: 'https://npm.pkg.github.com'
|
|
78
|
-
scope: '@multiplier-labs'
|
|
79
|
-
|
|
80
|
-
- run: npm ci
|
|
81
|
-
env:
|
|
82
|
-
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
For other CI systems, set `NODE_AUTH_TOKEN` environment variable to a PAT with `read:packages` scope.
|
|
86
|
-
|
|
87
|
-
## Versioning
|
|
88
|
-
|
|
89
|
-
Update the version in `package.json` before creating a release:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
npm version patch # 0.1.0 → 0.1.1
|
|
93
|
-
npm version minor # 0.1.0 → 0.2.0
|
|
94
|
-
npm version major # 0.1.0 → 1.0.0
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Then push the tag and create a release on GitHub.
|