@multiplier-labs/stepflow 0.3.3 → 0.3.6

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.
Files changed (84) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-GYPNJBDP.js → chunk-D5HW7VEG.js} +269 -48
  3. package/dist/chunk-D5HW7VEG.js.map +1 -0
  4. package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
  5. package/dist/chunk-FMPJBK2N.js.map +1 -0
  6. package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
  7. package/dist/chunk-JPXPB7U5.js.map +1 -0
  8. package/dist/{chunk-4VOKMGP6.js → chunk-JUPZEBR3.js} +519 -522
  9. package/dist/chunk-JUPZEBR3.js.map +1 -0
  10. package/dist/events/index.d.ts +58 -3
  11. package/dist/events/index.js +1 -1
  12. package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
  13. package/dist/index.d.ts +2 -12
  14. package/dist/index.js +764 -485
  15. package/dist/index.js.map +1 -1
  16. package/dist/scheduler/index.d.ts +1 -1
  17. package/dist/scheduler/index.js +2 -2
  18. package/dist/storage/index.d.ts +313 -47
  19. package/dist/storage/index.js +4 -2
  20. package/package.json +27 -13
  21. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  22. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  23. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  24. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  25. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  26. package/.github/CODEOWNERS +0 -15
  27. package/.github/dependabot.yml +0 -23
  28. package/.github/workflows/ci.yml +0 -38
  29. package/.github/workflows/publish.yml +0 -36
  30. package/.github/workflows/security.yml +0 -29
  31. package/CHANGELOG.md +0 -34
  32. package/SECURITY.md +0 -40
  33. package/dist/chunk-4VOKMGP6.js.map +0 -1
  34. package/dist/chunk-GYPNJBDP.js.map +0 -1
  35. package/dist/chunk-KHTUWLVZ.js.map +0 -1
  36. package/dist/chunk-ML35PIHX.js.map +0 -1
  37. package/docs/github-packages-setup.md +0 -97
  38. package/docs/stepflow-api-reference.md +0 -1661
  39. package/src/core/engine.test.ts +0 -1365
  40. package/src/core/engine.ts +0 -692
  41. package/src/core/index.ts +0 -7
  42. package/src/core/orchestrator.ts +0 -571
  43. package/src/core/types.ts +0 -220
  44. package/src/events/index.ts +0 -8
  45. package/src/events/memory.test.ts +0 -107
  46. package/src/events/memory.ts +0 -92
  47. package/src/events/socketio.test.ts +0 -378
  48. package/src/events/socketio.ts +0 -262
  49. package/src/events/types.ts +0 -118
  50. package/src/events/webhook.test.ts +0 -657
  51. package/src/events/webhook.ts +0 -564
  52. package/src/index.ts +0 -270
  53. package/src/planning/index.ts +0 -55
  54. package/src/planning/planner.ts +0 -576
  55. package/src/planning/planning.test.ts +0 -727
  56. package/src/planning/registry.test.ts +0 -398
  57. package/src/planning/registry.ts +0 -365
  58. package/src/planning/types.ts +0 -566
  59. package/src/scheduler/cron.test.ts +0 -471
  60. package/src/scheduler/cron.ts +0 -414
  61. package/src/scheduler/index.ts +0 -8
  62. package/src/scheduler/postgres-persistence.ts +0 -518
  63. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  64. package/src/scheduler/sqlite-persistence.ts +0 -261
  65. package/src/scheduler/types.ts +0 -73
  66. package/src/storage/index.ts +0 -27
  67. package/src/storage/memory.test.ts +0 -349
  68. package/src/storage/memory.ts +0 -227
  69. package/src/storage/postgres.ts +0 -1354
  70. package/src/storage/sqlite.test.ts +0 -466
  71. package/src/storage/sqlite.ts +0 -627
  72. package/src/storage/types.ts +0 -343
  73. package/src/utils/errors.test.ts +0 -150
  74. package/src/utils/errors.ts +0 -161
  75. package/src/utils/id.ts +0 -21
  76. package/src/utils/index.ts +0 -8
  77. package/src/utils/logger.test.ts +0 -121
  78. package/src/utils/logger.ts +0 -96
  79. package/src/utils/postgres-deps.ts +0 -63
  80. package/src/utils/retry.test.ts +0 -177
  81. package/src/utils/retry.ts +0 -121
  82. package/tsconfig.json +0 -21
  83. package/tsup.config.ts +0 -14
  84. package/vitest.config.ts +0 -9
@@ -1,8 +1,11 @@
1
1
  import {
2
2
  generateId,
3
3
  loadPostgresDeps,
4
+ requirePostgresDeps,
5
+ safeParseField,
6
+ safeParseOptionalField,
4
7
  sanitizeErrorForStorage
5
- } from "./chunk-ML35PIHX.js";
8
+ } from "./chunk-JPXPB7U5.js";
6
9
 
7
10
  // src/storage/memory.ts
8
11
  var MemoryStorageAdapter = class {
@@ -158,6 +161,52 @@ var MemoryStorageAdapter = class {
158
161
  }
159
162
  };
160
163
 
164
+ // src/storage/sqlite-core.ts
165
+ function mapRunRow(row, ctx) {
166
+ const fieldCtx = (column) => ({ ...ctx, rowId: row.id, column });
167
+ return {
168
+ id: row.id,
169
+ kind: row.kind,
170
+ status: row.status,
171
+ parentRunId: row.parent_run_id ?? void 0,
172
+ input: safeParseField(row.input_json, {}, fieldCtx("input_json")),
173
+ metadata: safeParseField(row.metadata_json, {}, fieldCtx("metadata_json")),
174
+ context: safeParseField(row.context_json, {}, fieldCtx("context_json")),
175
+ completedSteps: safeParseOptionalField(row.completed_steps_json, fieldCtx("completed_steps_json")),
176
+ error: safeParseOptionalField(row.error_json, fieldCtx("error_json")),
177
+ createdAt: new Date(row.created_at),
178
+ startedAt: row.started_at ? new Date(row.started_at) : void 0,
179
+ finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
180
+ };
181
+ }
182
+ function mapStepRow(row, ctx) {
183
+ const fieldCtx = (column) => ({ ...ctx, rowId: row.id, column });
184
+ return {
185
+ id: row.id,
186
+ runId: row.run_id,
187
+ stepKey: row.step_key,
188
+ stepName: row.step_name,
189
+ status: row.status,
190
+ attempt: row.attempt,
191
+ result: safeParseOptionalField(row.result_json, fieldCtx("result_json")),
192
+ error: safeParseOptionalField(row.error_json, fieldCtx("error_json")),
193
+ startedAt: row.started_at ? new Date(row.started_at) : void 0,
194
+ finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
195
+ };
196
+ }
197
+ function mapEventRow(row, ctx) {
198
+ const fieldCtx = { ...ctx, rowId: row.id, column: "payload_json" };
199
+ return {
200
+ id: row.id,
201
+ runId: row.run_id,
202
+ stepKey: row.step_key ?? void 0,
203
+ eventType: row.event_type,
204
+ level: row.level,
205
+ payload: safeParseOptionalField(row.payload_json, fieldCtx),
206
+ timestamp: new Date(row.timestamp)
207
+ };
208
+ }
209
+
161
210
  // src/storage/sqlite.ts
162
211
  var CREATE_TABLES_SQL = `
163
212
  -- Workflow runs table
@@ -214,18 +263,45 @@ CREATE TABLE IF NOT EXISTS workflow_events (
214
263
  CREATE INDEX IF NOT EXISTS idx_workflow_events_run ON workflow_events(run_id);
215
264
  CREATE INDEX IF NOT EXISTS idx_workflow_events_run_ts ON workflow_events(run_id, timestamp);
216
265
  `;
266
+ var SCHEMA_MIGRATIONS = [
267
+ { table: "workflow_runs", column: "completed_steps_json", type: "TEXT" }
268
+ ];
269
+ function isDuplicateColumnError(err) {
270
+ if (!err || typeof err !== "object") return false;
271
+ const code = err.code;
272
+ const message = err.message;
273
+ return code === "SQLITE_ERROR" && typeof message === "string" && message.toLowerCase().includes("duplicate column name");
274
+ }
275
+ async function isPromiseSettled(p) {
276
+ let settled = false;
277
+ p.then(
278
+ () => {
279
+ settled = true;
280
+ },
281
+ () => {
282
+ settled = true;
283
+ }
284
+ );
285
+ for (let i = 0; i < 5 && !settled; i++) {
286
+ await Promise.resolve();
287
+ }
288
+ return settled;
289
+ }
217
290
  var SQLiteStorageAdapter = class {
218
291
  db;
219
292
  prefix;
293
+ logger;
220
294
  // Prepared statements (cached for performance)
221
295
  stmts = null;
222
296
  constructor(config) {
223
297
  this.db = config.db;
224
298
  this.prefix = config.tablePrefix ?? "workflow";
299
+ this.logger = config.logger;
225
300
  this.db.pragma("foreign_keys = ON");
226
301
  if (config.autoCreateTables !== false) {
227
302
  this.createTables();
228
303
  }
304
+ this.runMigrations();
229
305
  this.prepareStatements();
230
306
  }
231
307
  /**
@@ -234,6 +310,36 @@ var SQLiteStorageAdapter = class {
234
310
  createTables() {
235
311
  this.db.exec(CREATE_TABLES_SQL);
236
312
  }
313
+ /**
314
+ * Apply additive schema migrations to existing tables.
315
+ *
316
+ * `CREATE TABLE IF NOT EXISTS` is a no-op when the table already exists, so
317
+ * any column added to {@link CREATE_TABLES_SQL} after the initial schema is
318
+ * silently absent on databases created by older versions of this adapter.
319
+ * Without this step, the first INSERT/SELECT crashes with
320
+ * `SqliteError: table X has no column named Y`. This mirrors the
321
+ * `ALTER TABLE ADD COLUMN IF NOT EXISTS` block in `postgres.ts`'s
322
+ * `createTables()`.
323
+ *
324
+ * For each entry in {@link SCHEMA_MIGRATIONS}: if the table exists and the
325
+ * column does not, run `ALTER TABLE ADD COLUMN`. The ALTER is wrapped in a
326
+ * try/catch that tolerates `duplicate column` errors so concurrent startups
327
+ * (or any race between the existence check and the ALTER) do not crash.
328
+ * Other SQLite errors propagate.
329
+ */
330
+ runMigrations() {
331
+ for (const { table, column, type } of SCHEMA_MIGRATIONS) {
332
+ const tableInfo = this.db.prepare(`PRAGMA table_info(${table})`).all();
333
+ if (tableInfo.length === 0) continue;
334
+ if (tableInfo.some((col) => col.name === column)) continue;
335
+ try {
336
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
337
+ } catch (err) {
338
+ if (isDuplicateColumnError(err)) continue;
339
+ throw err;
340
+ }
341
+ }
342
+ }
237
343
  /**
238
344
  * Prepare all SQL statements for better performance.
239
345
  */
@@ -356,7 +462,7 @@ var SQLiteStorageAdapter = class {
356
462
  }
357
463
  async getRun(runId) {
358
464
  const row = this.stmts.getRun.get(runId);
359
- return row ? this.mapRunRow(row) : null;
465
+ return row ? mapRunRow(row, this.mapperCtx) : null;
360
466
  }
361
467
  async updateRun(runId, updates) {
362
468
  this.stmts.updateRun.run(
@@ -395,7 +501,7 @@ var SQLiteStorageAdapter = class {
395
501
  parentRunId
396
502
  );
397
503
  return {
398
- items: rows.map((row) => this.mapRunRow(row)),
504
+ items: rows.map((row) => mapRunRow(row, this.mapperCtx)),
399
505
  total: countResult.count,
400
506
  limit,
401
507
  offset
@@ -422,7 +528,7 @@ var SQLiteStorageAdapter = class {
422
528
  }
423
529
  async getStep(stepId) {
424
530
  const row = this.stmts.getStep.get(stepId);
425
- return row ? this.mapStepRow(row) : null;
531
+ return row ? mapStepRow(row, this.mapperCtx) : null;
426
532
  }
427
533
  async updateStep(stepId, updates) {
428
534
  this.stmts.updateStep.run(
@@ -436,7 +542,7 @@ var SQLiteStorageAdapter = class {
436
542
  }
437
543
  async getStepsForRun(runId) {
438
544
  const rows = this.stmts.getStepsForRun.all(runId);
439
- return rows.map((row) => this.mapStepRow(row));
545
+ return rows.map((row) => mapStepRow(row, this.mapperCtx));
440
546
  }
441
547
  // ============================================================================
442
548
  // Event Operations
@@ -467,7 +573,7 @@ var SQLiteStorageAdapter = class {
467
573
  limit,
468
574
  offset
469
575
  );
470
- return rows.map((row) => this.mapEventRow(row));
576
+ return rows.map((row) => mapEventRow(row, this.mapperCtx));
471
577
  }
472
578
  // ============================================================================
473
579
  // Transaction Support
@@ -475,16 +581,28 @@ var SQLiteStorageAdapter = class {
475
581
  /**
476
582
  * Execute a function within a database transaction (async interface).
477
583
  *
478
- * @deprecated Use `transactionSync()` instead. This method only works when
479
- * the callback performs purely synchronous operations wrapped in async/await.
480
- * If the callback awaits real async I/O (network, timers, etc.), it will
481
- * throw an error. `transactionSync()` makes the synchronous requirement explicit.
584
+ * @deprecated Use {@link transactionSync} instead. better-sqlite3 transactions
585
+ * are synchronous: COMMIT happens the moment the callback returns control to
586
+ * `db.transaction(fn)()`. If the callback awaits real async I/O (network,
587
+ * timers, etc.), the COMMIT has already happened by the time the awaited work
588
+ * runs, so its operations are **not** transactional.
589
+ *
590
+ * To make the footgun visible the adapter installs a runtime guard: when the
591
+ * callback's promise has not settled by the time the COMMIT returns, the
592
+ * deprecated method emits a `console.warn` so that the misuse surfaces in
593
+ * production logs. The promise is still returned so existing callers keep
594
+ * working — switch to `transactionSync` to silence the warning.
482
595
  */
483
596
  async transaction(fn) {
484
597
  let fnPromise;
485
598
  this.transactionSync(() => {
486
599
  fnPromise = fn(this);
487
600
  });
601
+ if (!await isPromiseSettled(fnPromise)) {
602
+ console.warn(
603
+ "[SQLiteStorageAdapter] transaction() callback did not settle synchronously. better-sqlite3 transactions are synchronous and the COMMIT has already executed; any async work inside the callback is NOT transactional. Use transactionSync() with purely synchronous operations instead."
604
+ );
605
+ }
488
606
  return fnPromise;
489
607
  }
490
608
  /**
@@ -528,7 +646,7 @@ var SQLiteStorageAdapter = class {
528
646
  */
529
647
  async getInterruptedRuns() {
530
648
  const rows = this.stmts.getInterruptedRuns.all();
531
- return rows.map((row) => this.mapRunRow(row));
649
+ return rows.map((row) => mapRunRow(row, this.mapperCtx));
532
650
  }
533
651
  /**
534
652
  * Get the last completed step for a run.
@@ -536,51 +654,20 @@ var SQLiteStorageAdapter = class {
536
654
  */
537
655
  async getLastCompletedStep(runId) {
538
656
  const row = this.stmts.getLastCompletedStep.get(runId);
539
- return row ? this.mapStepRow(row) : null;
657
+ return row ? mapStepRow(row, this.mapperCtx) : null;
540
658
  }
541
659
  // ============================================================================
542
660
  // Row Mapping
543
661
  // ============================================================================
544
- mapRunRow(row) {
545
- return {
546
- id: row.id,
547
- kind: row.kind,
548
- status: row.status,
549
- parentRunId: row.parent_run_id ?? void 0,
550
- input: JSON.parse(row.input_json),
551
- metadata: JSON.parse(row.metadata_json),
552
- context: JSON.parse(row.context_json),
553
- completedSteps: row.completed_steps_json ? JSON.parse(row.completed_steps_json) : void 0,
554
- error: row.error_json ? JSON.parse(row.error_json) : void 0,
555
- createdAt: new Date(row.created_at),
556
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
557
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
558
- };
559
- }
560
- mapStepRow(row) {
561
- return {
562
- id: row.id,
563
- runId: row.run_id,
564
- stepKey: row.step_key,
565
- stepName: row.step_name,
566
- status: row.status,
567
- attempt: row.attempt,
568
- result: row.result_json ? JSON.parse(row.result_json) : void 0,
569
- error: row.error_json ? JSON.parse(row.error_json) : void 0,
570
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
571
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
572
- };
573
- }
574
- mapEventRow(row) {
575
- return {
576
- id: row.id,
577
- runId: row.run_id,
578
- stepKey: row.step_key ?? void 0,
579
- eventType: row.event_type,
580
- level: row.level,
581
- payload: row.payload_json ? JSON.parse(row.payload_json) : void 0,
582
- timestamp: new Date(row.timestamp)
583
- };
662
+ //
663
+ // The pure row mappers (`mapRunRow`, `mapStepRow`, `mapEventRow`) live in
664
+ // `./sqlite-core.ts`. Each call site here builds a `MapperContext` once via
665
+ // `this.mapperCtx` and hands it to the mapper, which extends it with
666
+ // `rowId` + `column` per JSON-parse so corruption logs identify the bad row
667
+ // (audit M2 / L3 — PR #52).
668
+ /** Mapper context paired with the adapter's component label + optional logger. */
669
+ get mapperCtx() {
670
+ return { component: "SQLiteStorageAdapter", logger: this.logger };
584
671
  }
585
672
  // ============================================================================
586
673
  // Utility Methods
@@ -606,6 +693,268 @@ var SQLiteStorageAdapter = class {
606
693
  }
607
694
  };
608
695
 
696
+ // src/storage/postgres-core.ts
697
+ function safeParseField2(value, fallback, ctx) {
698
+ return safeParseField(value, fallback, ctx);
699
+ }
700
+ function safeParseOptionalField2(value, ctx) {
701
+ return safeParseOptionalField(value, ctx);
702
+ }
703
+ function mapRunRow2(row, ctx) {
704
+ const fieldCtx = (column) => ({ ...ctx, rowId: row.id, column });
705
+ return {
706
+ id: row.id,
707
+ kind: row.kind,
708
+ status: row.status,
709
+ parentRunId: row.parent_run_id ?? void 0,
710
+ input: safeParseField2(row.input_json, {}, fieldCtx("input_json")),
711
+ context: safeParseField2(row.context_json, {}, fieldCtx("context_json")),
712
+ output: safeParseOptionalField2(row.output_json, fieldCtx("output_json")),
713
+ error: safeParseOptionalField2(row.error_json, fieldCtx("error_json")),
714
+ metadata: safeParseField2(row.metadata_json, {}, fieldCtx("metadata_json")),
715
+ priority: row.priority ?? 0,
716
+ timeoutMs: row.timeout_ms ?? void 0,
717
+ createdAt: new Date(row.created_at),
718
+ startedAt: row.started_at ? new Date(row.started_at) : void 0,
719
+ finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
720
+ };
721
+ }
722
+ function mapStepRow2(row, ctx) {
723
+ const fieldCtx = (column) => ({ ...ctx, rowId: row.id, column });
724
+ return {
725
+ id: row.id,
726
+ runId: row.run_id,
727
+ stepKey: row.step_key,
728
+ stepName: row.step_name,
729
+ status: row.status,
730
+ attempt: row.attempt,
731
+ result: safeParseOptionalField2(row.result_json, fieldCtx("result_json")),
732
+ error: safeParseOptionalField2(row.error_json, fieldCtx("error_json")),
733
+ startedAt: row.started_at ? new Date(row.started_at) : void 0,
734
+ finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
735
+ };
736
+ }
737
+ function mapEventRow2(row, ctx) {
738
+ const fieldCtx = { ...ctx, rowId: row.id, column: "payload_json" };
739
+ return {
740
+ id: row.id,
741
+ runId: row.run_id,
742
+ stepKey: row.step_key ?? void 0,
743
+ eventType: row.event_type,
744
+ level: row.level,
745
+ payload: safeParseOptionalField2(row.payload_json, fieldCtx),
746
+ timestamp: new Date(row.timestamp)
747
+ };
748
+ }
749
+ function applyRunsFilters(query, options) {
750
+ if (options.kind) {
751
+ query = query.where("kind", "=", options.kind);
752
+ }
753
+ if (options.status) {
754
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
755
+ query = query.where("status", "in", statuses);
756
+ }
757
+ if (options.parentRunId !== void 0) {
758
+ query = query.where("parent_run_id", "=", options.parentRunId);
759
+ }
760
+ return query;
761
+ }
762
+ var PostgresStorageCore = class {
763
+ constructor(schema, logger) {
764
+ this.schema = schema;
765
+ this.logger = logger;
766
+ }
767
+ schema;
768
+ logger;
769
+ /** Mapper context handed to row mappers — pairs the component label with the optional logger. */
770
+ get mapperCtx() {
771
+ return { component: this.warnLabel, logger: this.logger };
772
+ }
773
+ /**
774
+ * Hook called at the start of every CRUD method. The pool-owning adapter
775
+ * overrides this to throw before `initialize()` has run; the transaction
776
+ * wrapper leaves it as a no-op.
777
+ */
778
+ ensureReady() {
779
+ }
780
+ // --------------------------------------------------------------------------
781
+ // Run operations
782
+ // --------------------------------------------------------------------------
783
+ async createRun(run) {
784
+ this.ensureReady();
785
+ const id = "id" in run && run.id ? run.id : generateId();
786
+ const createdAt = /* @__PURE__ */ new Date();
787
+ await this.qb.insertInto("runs").values({
788
+ id,
789
+ kind: run.kind,
790
+ status: run.status,
791
+ parent_run_id: "parentRunId" in run ? run.parentRunId ?? null : null,
792
+ input_json: JSON.stringify(run.input),
793
+ metadata_json: JSON.stringify(run.metadata ?? {}),
794
+ context_json: JSON.stringify(run.context ?? {}),
795
+ output_json: null,
796
+ error_json: "error" in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
797
+ priority: "priority" in run ? run.priority ?? 0 : 0,
798
+ timeout_ms: "timeoutMs" in run ? run.timeoutMs ?? null : null,
799
+ created_at: createdAt,
800
+ started_at: null,
801
+ finished_at: null
802
+ }).execute();
803
+ return {
804
+ id,
805
+ kind: run.kind,
806
+ status: run.status,
807
+ parentRunId: "parentRunId" in run ? run.parentRunId : void 0,
808
+ input: run.input,
809
+ context: run.context ?? {},
810
+ output: void 0,
811
+ error: void 0,
812
+ metadata: run.metadata ?? {},
813
+ priority: "priority" in run ? run.priority ?? 0 : 0,
814
+ timeoutMs: "timeoutMs" in run ? run.timeoutMs : void 0,
815
+ createdAt
816
+ };
817
+ }
818
+ async getRun(runId) {
819
+ this.ensureReady();
820
+ const row = await this.qb.selectFrom("runs").selectAll().where("id", "=", runId).executeTakeFirst();
821
+ return row ? mapRunRow2(row, this.mapperCtx) : null;
822
+ }
823
+ async updateRun(runId, updates) {
824
+ this.ensureReady();
825
+ const updateData = {};
826
+ if (updates.status !== void 0) {
827
+ updateData.status = updates.status;
828
+ }
829
+ if (updates.context !== void 0) {
830
+ updateData.context_json = JSON.stringify(updates.context);
831
+ }
832
+ if ("output" in updates && updates.output !== void 0) {
833
+ updateData.output_json = JSON.stringify(updates.output);
834
+ }
835
+ if (updates.error !== void 0) {
836
+ updateData.error_json = JSON.stringify(
837
+ sanitizeErrorForStorage(updates.error)
838
+ );
839
+ }
840
+ if (updates.startedAt !== void 0) {
841
+ updateData.started_at = updates.startedAt;
842
+ }
843
+ if (updates.finishedAt !== void 0) {
844
+ updateData.finished_at = updates.finishedAt;
845
+ }
846
+ if (Object.keys(updateData).length > 0) {
847
+ await this.qb.updateTable("runs").set(updateData).where("id", "=", runId).execute();
848
+ }
849
+ }
850
+ async listRuns(options = {}) {
851
+ this.ensureReady();
852
+ const { sql: sql2 } = requirePostgresDeps();
853
+ const limit = options.limit ?? 50;
854
+ const offset = options.offset ?? 0;
855
+ const countQuery = applyRunsFilters(
856
+ this.qb.selectFrom("runs").select(sql2`count(*)`.as("count")),
857
+ options
858
+ );
859
+ const dataQuery = applyRunsFilters(this.qb.selectFrom("runs").selectAll(), options);
860
+ const orderBy = options.orderBy ?? "createdAt";
861
+ const orderDirection = options.orderDirection ?? "desc";
862
+ const orderColumn = orderBy === "createdAt" ? "created_at" : orderBy === "startedAt" ? "started_at" : "finished_at";
863
+ const countResult = await countQuery.executeTakeFirst();
864
+ const total = Number(countResult?.count ?? 0);
865
+ const rows = await dataQuery.orderBy(orderColumn, orderDirection).limit(limit).offset(offset).execute();
866
+ return {
867
+ items: rows.map((row) => mapRunRow2(row, this.mapperCtx)),
868
+ total
869
+ };
870
+ }
871
+ // --------------------------------------------------------------------------
872
+ // Step operations
873
+ // --------------------------------------------------------------------------
874
+ async createStep(step) {
875
+ this.ensureReady();
876
+ const id = generateId();
877
+ await this.qb.insertInto("workflow_run_steps").values({
878
+ id,
879
+ run_id: step.runId,
880
+ step_key: step.stepKey,
881
+ step_name: step.stepName,
882
+ status: step.status,
883
+ attempt: step.attempt,
884
+ result_json: step.result !== void 0 ? JSON.stringify(step.result) : null,
885
+ error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
886
+ started_at: step.startedAt ?? null,
887
+ finished_at: step.finishedAt ?? null
888
+ }).execute();
889
+ return { ...step, id };
890
+ }
891
+ async getStep(stepId) {
892
+ this.ensureReady();
893
+ const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("id", "=", stepId).executeTakeFirst();
894
+ return row ? mapStepRow2(row, this.mapperCtx) : null;
895
+ }
896
+ async updateStep(stepId, updates) {
897
+ this.ensureReady();
898
+ const updateData = {};
899
+ if (updates.status !== void 0) {
900
+ updateData.status = updates.status;
901
+ }
902
+ if (updates.attempt !== void 0) {
903
+ updateData.attempt = updates.attempt;
904
+ }
905
+ if (updates.result !== void 0) {
906
+ updateData.result_json = JSON.stringify(updates.result);
907
+ }
908
+ if (updates.error !== void 0) {
909
+ updateData.error_json = JSON.stringify(
910
+ sanitizeErrorForStorage(updates.error)
911
+ );
912
+ }
913
+ if (updates.finishedAt !== void 0) {
914
+ updateData.finished_at = updates.finishedAt;
915
+ }
916
+ if (Object.keys(updateData).length > 0) {
917
+ await this.qb.updateTable("workflow_run_steps").set(updateData).where("id", "=", stepId).execute();
918
+ }
919
+ }
920
+ async getStepsForRun(runId) {
921
+ this.ensureReady();
922
+ const rows = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
923
+ return rows.map((row) => mapStepRow2(row, this.mapperCtx));
924
+ }
925
+ // --------------------------------------------------------------------------
926
+ // Event operations
927
+ // --------------------------------------------------------------------------
928
+ async saveEvent(event) {
929
+ this.ensureReady();
930
+ const id = generateId();
931
+ await this.qb.insertInto("workflow_events").values({
932
+ id,
933
+ run_id: event.runId,
934
+ step_key: event.stepKey ?? null,
935
+ event_type: event.eventType,
936
+ level: event.level,
937
+ payload_json: event.payload !== void 0 ? JSON.stringify(event.payload) : null,
938
+ timestamp: event.timestamp
939
+ }).execute();
940
+ }
941
+ async getEventsForRun(runId, options = {}) {
942
+ this.ensureReady();
943
+ const limit = options.limit ?? 1e3;
944
+ const offset = options.offset ?? 0;
945
+ let query = this.qb.selectFrom("workflow_events").selectAll().where("run_id", "=", runId);
946
+ if (options.stepKey) {
947
+ query = query.where("step_key", "=", options.stepKey);
948
+ }
949
+ if (options.level) {
950
+ query = query.where("level", "=", options.level);
951
+ }
952
+ query = query.orderBy("timestamp", "asc").limit(limit).offset(offset);
953
+ const rows = await query.execute();
954
+ return rows.map((row) => mapEventRow2(row, this.mapperCtx));
955
+ }
956
+ };
957
+
609
958
  // src/storage/postgres.ts
610
959
  var Kysely;
611
960
  var PostgresDialect;
@@ -615,31 +964,48 @@ function stripStack(error) {
615
964
  const { stack: _stack, ...rest } = error;
616
965
  return rest;
617
966
  }
618
- var PostgresStorageAdapter = class {
967
+ var PostgresStorageAdapter = class _PostgresStorageAdapter extends PostgresStorageCore {
619
968
  db;
620
969
  pool;
621
970
  ownsPool = false;
622
- schema;
623
971
  autoMigrate;
624
972
  initialized = false;
625
973
  config;
626
974
  constructor(config) {
627
- this.schema = config.schema ?? "public";
628
- if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {
975
+ const schema = config.schema ?? "public";
976
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(schema)) {
629
977
  throw new Error(
630
- `Invalid schema name "${this.schema}". Schema must start with a letter or underscore, contain only alphanumeric characters and underscores, and be at most 63 characters.`
978
+ `Invalid schema name "${schema}". Schema must start with a letter or underscore, contain only alphanumeric characters and underscores, and be at most 63 characters.`
631
979
  );
632
980
  }
981
+ super(schema, config.logger);
633
982
  this.autoMigrate = config.autoMigrate !== false;
634
983
  this.config = config;
635
984
  }
636
985
  /**
637
- * Get a schema-scoped query builder.
638
- * All queries MUST use this instead of this.db directly to respect config.schema.
986
+ * Core-tables query builder consumed by `PostgresStorageCore`. Returns the
987
+ * schema-scoped Kysely view typed against the three core tables, which is
988
+ * what the shared CRUD methods need.
639
989
  */
640
990
  get qb() {
641
991
  return this.db.withSchema(this.schema);
642
992
  }
993
+ /**
994
+ * Extended query builder for adapter-only tables (currently
995
+ * `stepflow_step_results`). Kept separate from `qb` so the base class stays
996
+ * typed against the core schema.
997
+ */
998
+ get extQb() {
999
+ return this.db.withSchema(this.schema);
1000
+ }
1001
+ /** Tag used by shared row mappers in `console.warn` messages. */
1002
+ get warnLabel() {
1003
+ return "PostgresStorageAdapter";
1004
+ }
1005
+ /** Override of `PostgresStorageCore.ensureReady()` — enforces `initialize()`. */
1006
+ ensureReady() {
1007
+ this.ensureInitialized();
1008
+ }
643
1009
  ensureInitialized() {
644
1010
  if (!this.initialized) {
645
1011
  throw new Error(
@@ -694,6 +1060,44 @@ var PostgresStorageAdapter = class {
694
1060
  // ============================================================================
695
1061
  // Schema Creation
696
1062
  // ============================================================================
1063
+ /**
1064
+ * Run a single migration step. Tolerates only known-benign Postgres errors
1065
+ * (duplicate object codes that map to "already applied"), and rethrows
1066
+ * everything else with a clear message so partial-state upgrades are not
1067
+ * silently swallowed.
1068
+ */
1069
+ async runMigration(label, fn) {
1070
+ try {
1071
+ await fn();
1072
+ } catch (err) {
1073
+ if (_PostgresStorageAdapter.isBenignMigrationError(err)) {
1074
+ return;
1075
+ }
1076
+ const message = err instanceof Error ? err.message : String(err);
1077
+ throw new Error(`Schema migration failed (${label}): ${message}`, { cause: err });
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Postgres SQLSTATE codes that indicate a migration step has already been
1082
+ * applied (object already exists). These are safe to ignore on idempotent
1083
+ * re-runs; everything else must propagate.
1084
+ *
1085
+ * - 42701: duplicate_column
1086
+ * - 42P07: duplicate_table
1087
+ * - 42P06: duplicate_schema
1088
+ * - 42710: duplicate_object (e.g., index, constraint)
1089
+ */
1090
+ static BENIGN_MIGRATION_CODES = /* @__PURE__ */ new Set([
1091
+ "42701",
1092
+ "42P07",
1093
+ "42P06",
1094
+ "42710"
1095
+ ]);
1096
+ static isBenignMigrationError(err) {
1097
+ if (!err || typeof err !== "object") return false;
1098
+ const code = err.code;
1099
+ return typeof code === "string" && _PostgresStorageAdapter.BENIGN_MIGRATION_CODES.has(code);
1100
+ }
697
1101
  /**
698
1102
  * Create the workflow tables if they don't exist.
699
1103
  */
@@ -722,13 +1126,15 @@ var PostgresStorageAdapter = class {
722
1126
  )
723
1127
  )
724
1128
  `.execute(this.db);
725
- await sql`
726
- ALTER TABLE ${sql.table(`${this.schema}.runs`)}
727
- ADD COLUMN IF NOT EXISTS output_json JSONB,
728
- ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,
729
- ADD COLUMN IF NOT EXISTS timeout_ms INTEGER
730
- `.execute(this.db).catch(() => {
731
- });
1129
+ await this.runMigration(
1130
+ `add columns to ${this.schema}.runs`,
1131
+ () => sql`
1132
+ ALTER TABLE ${sql.table(`${this.schema}.runs`)}
1133
+ ADD COLUMN IF NOT EXISTS output_json JSONB,
1134
+ ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,
1135
+ ADD COLUMN IF NOT EXISTS timeout_ms INTEGER
1136
+ `.execute(this.db)
1137
+ );
732
1138
  await sql`
733
1139
  CREATE INDEX IF NOT EXISTS idx_runs_kind_status
734
1140
  ON ${sql.table(`${this.schema}.runs`)} (kind, status)
@@ -823,220 +1229,24 @@ var PostgresStorageAdapter = class {
823
1229
  `.execute(this.db);
824
1230
  }
825
1231
  // ============================================================================
826
- // Run Operations
1232
+ // Run / Step / Event CRUD
1233
+ // ============================================================================
1234
+ //
1235
+ // The CRUD methods (`createRun`, `getRun`, `updateRun`, `listRuns`,
1236
+ // `createStep`, `getStep`, `updateStep`, `getStepsForRun`, `saveEvent`,
1237
+ // `getEventsForRun`) are inherited from `PostgresStorageCore`. Each call
1238
+ // routes through `this.qb` (overridden above) and `this.ensureReady()`
1239
+ // which calls `this.ensureInitialized()`.
1240
+ // ============================================================================
1241
+ // Transaction Support
827
1242
  // ============================================================================
828
1243
  /**
829
- * Create a new workflow run.
830
- * Supports both legacy and new CreateRunInput interfaces.
1244
+ * Execute a function within a database transaction.
831
1245
  */
832
- async createRun(run) {
833
- this.ensureInitialized();
834
- const id = "id" in run && run.id ? run.id : generateId();
835
- const createdAt = /* @__PURE__ */ new Date();
836
- await this.qb.insertInto("runs").values({
837
- id,
838
- kind: run.kind,
839
- status: run.status,
840
- parent_run_id: "parentRunId" in run ? run.parentRunId ?? null : null,
841
- input_json: JSON.stringify(run.input),
842
- metadata_json: JSON.stringify(run.metadata ?? {}),
843
- context_json: JSON.stringify(run.context ?? {}),
844
- // Default to empty object
845
- output_json: null,
846
- error_json: "error" in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
847
- priority: "priority" in run ? run.priority ?? 0 : 0,
848
- timeout_ms: "timeoutMs" in run ? run.timeoutMs ?? null : null,
849
- created_at: createdAt,
850
- started_at: null,
851
- finished_at: null
852
- }).execute();
853
- return {
854
- id,
855
- kind: run.kind,
856
- status: run.status,
857
- parentRunId: "parentRunId" in run ? run.parentRunId : void 0,
858
- input: run.input,
859
- context: run.context ?? {},
860
- output: void 0,
861
- error: void 0,
862
- metadata: run.metadata ?? {},
863
- priority: "priority" in run ? run.priority ?? 0 : 0,
864
- timeoutMs: "timeoutMs" in run ? run.timeoutMs : void 0,
865
- createdAt
866
- };
867
- }
868
- async getRun(runId) {
869
- this.ensureInitialized();
870
- const row = await this.qb.selectFrom("runs").selectAll().where("id", "=", runId).executeTakeFirst();
871
- return row ? this.mapRunRow(row) : null;
872
- }
873
- /**
874
- * Update a workflow run.
875
- * Supports both legacy Partial<WorkflowRunRecord> and new UpdateRunInput interfaces.
876
- */
877
- async updateRun(runId, updates) {
878
- this.ensureInitialized();
879
- const updateData = {};
880
- if (updates.status !== void 0) {
881
- updateData.status = updates.status;
882
- }
883
- if (updates.context !== void 0) {
884
- updateData.context_json = JSON.stringify(updates.context);
885
- }
886
- if ("output" in updates && updates.output !== void 0) {
887
- updateData.output_json = JSON.stringify(updates.output);
888
- }
889
- if (updates.error !== void 0) {
890
- updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
891
- }
892
- if (updates.startedAt !== void 0) {
893
- updateData.started_at = updates.startedAt;
894
- }
895
- if (updates.finishedAt !== void 0) {
896
- updateData.finished_at = updates.finishedAt;
897
- }
898
- if (Object.keys(updateData).length > 0) {
899
- await this.qb.updateTable("runs").set(updateData).where("id", "=", runId).execute();
900
- }
901
- }
902
- /**
903
- * List workflow runs with filtering and pagination.
904
- */
905
- /**
906
- * Apply common run filters to a Kysely query builder.
907
- * Used by both the data query and count query in listRuns to avoid duplication.
908
- */
909
- applyRunsFilters(query, options) {
910
- if (options.kind) {
911
- query = query.where("kind", "=", options.kind);
912
- }
913
- if (options.status) {
914
- const statuses = Array.isArray(options.status) ? options.status : [options.status];
915
- query = query.where("status", "in", statuses);
916
- }
917
- if (options.parentRunId !== void 0) {
918
- query = query.where("parent_run_id", "=", options.parentRunId);
919
- }
920
- return query;
921
- }
922
- async listRuns(options = {}) {
923
- this.ensureInitialized();
924
- const limit = options.limit ?? 50;
925
- const offset = options.offset ?? 0;
926
- let query = this.applyRunsFilters(
927
- this.qb.selectFrom("runs").selectAll(),
928
- options
929
- );
930
- const countQuery = this.applyRunsFilters(
931
- this.qb.selectFrom("runs").select(sql`count(*)`.as("count")),
932
- options
933
- );
934
- const countResult = await countQuery.executeTakeFirst();
935
- const total = Number(countResult?.count ?? 0);
936
- const orderBy = options.orderBy ?? "createdAt";
937
- const orderDirection = options.orderDirection ?? "desc";
938
- const orderColumn = orderBy === "createdAt" ? "created_at" : orderBy === "startedAt" ? "started_at" : "finished_at";
939
- query = query.orderBy(orderColumn, orderDirection);
940
- query = query.limit(limit).offset(offset);
941
- const rows = await query.execute();
942
- return {
943
- items: rows.map((row) => this.mapRunRow(row)),
944
- total
945
- };
946
- }
947
- // ============================================================================
948
- // Step Operations
949
- // ============================================================================
950
- async createStep(step) {
951
- this.ensureInitialized();
952
- const id = generateId();
953
- await this.qb.insertInto("workflow_run_steps").values({
954
- id,
955
- run_id: step.runId,
956
- step_key: step.stepKey,
957
- step_name: step.stepName,
958
- status: step.status,
959
- attempt: step.attempt,
960
- result_json: step.result !== void 0 ? JSON.stringify(step.result) : null,
961
- error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
962
- started_at: step.startedAt ?? null,
963
- finished_at: step.finishedAt ?? null
964
- }).execute();
965
- return { ...step, id };
966
- }
967
- async getStep(stepId) {
968
- this.ensureInitialized();
969
- const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("id", "=", stepId).executeTakeFirst();
970
- return row ? this.mapStepRow(row) : null;
971
- }
972
- async updateStep(stepId, updates) {
973
- this.ensureInitialized();
974
- const updateData = {};
975
- if (updates.status !== void 0) {
976
- updateData.status = updates.status;
977
- }
978
- if (updates.attempt !== void 0) {
979
- updateData.attempt = updates.attempt;
980
- }
981
- if (updates.result !== void 0) {
982
- updateData.result_json = JSON.stringify(updates.result);
983
- }
984
- if (updates.error !== void 0) {
985
- updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
986
- }
987
- if (updates.finishedAt !== void 0) {
988
- updateData.finished_at = updates.finishedAt;
989
- }
990
- if (Object.keys(updateData).length > 0) {
991
- await this.qb.updateTable("workflow_run_steps").set(updateData).where("id", "=", stepId).execute();
992
- }
993
- }
994
- async getStepsForRun(runId) {
995
- this.ensureInitialized();
996
- const rows = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
997
- return rows.map((row) => this.mapStepRow(row));
998
- }
999
- // ============================================================================
1000
- // Event Operations
1001
- // ============================================================================
1002
- async saveEvent(event) {
1003
- this.ensureInitialized();
1004
- const id = generateId();
1005
- await this.qb.insertInto("workflow_events").values({
1006
- id,
1007
- run_id: event.runId,
1008
- step_key: event.stepKey ?? null,
1009
- event_type: event.eventType,
1010
- level: event.level,
1011
- payload_json: event.payload !== void 0 ? JSON.stringify(event.payload) : null,
1012
- timestamp: event.timestamp
1013
- }).execute();
1014
- }
1015
- async getEventsForRun(runId, options = {}) {
1016
- this.ensureInitialized();
1017
- const limit = options.limit ?? 1e3;
1018
- const offset = options.offset ?? 0;
1019
- let query = this.qb.selectFrom("workflow_events").selectAll().where("run_id", "=", runId);
1020
- if (options.stepKey) {
1021
- query = query.where("step_key", "=", options.stepKey);
1022
- }
1023
- if (options.level) {
1024
- query = query.where("level", "=", options.level);
1025
- }
1026
- query = query.orderBy("timestamp", "asc").limit(limit).offset(offset);
1027
- const rows = await query.execute();
1028
- return rows.map((row) => this.mapEventRow(row));
1029
- }
1030
- // ============================================================================
1031
- // Transaction Support
1032
- // ============================================================================
1033
- /**
1034
- * Execute a function within a database transaction.
1035
- */
1036
- async transaction(fn) {
1246
+ async transaction(fn) {
1037
1247
  this.ensureInitialized();
1038
1248
  return await this.db.transaction().execute(async (trx) => {
1039
- const txAdapter = new PostgresTransactionAdapter(trx, this.schema);
1249
+ const txAdapter = new PostgresTransactionAdapter(trx, this.schema, this.logger);
1040
1250
  return await fn(txAdapter);
1041
1251
  });
1042
1252
  }
@@ -1062,7 +1272,7 @@ var PostgresStorageAdapter = class {
1062
1272
  async getInterruptedRuns() {
1063
1273
  this.ensureInitialized();
1064
1274
  const rows = await this.qb.selectFrom("runs").selectAll().where("status", "in", ["queued", "running"]).orderBy("created_at", "asc").execute();
1065
- return rows.map((row) => this.mapRunRow(row));
1275
+ return rows.map((row) => mapRunRow2(row, this.mapperCtx));
1066
1276
  }
1067
1277
  /**
1068
1278
  * Get the last completed step for a run.
@@ -1071,7 +1281,7 @@ var PostgresStorageAdapter = class {
1071
1281
  async getLastCompletedStep(runId) {
1072
1282
  this.ensureInitialized();
1073
1283
  const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).where("status", "=", "succeeded").orderBy("finished_at", "desc").limit(1).executeTakeFirst();
1074
- return row ? this.mapStepRow(row) : null;
1284
+ return row ? mapStepRow2(row, this.mapperCtx) : null;
1075
1285
  }
1076
1286
  // ============================================================================
1077
1287
  // Atomic Dequeue (for distributed workers)
@@ -1099,87 +1309,37 @@ var PostgresStorageAdapter = class {
1099
1309
  RETURNING *
1100
1310
  `.execute(this.db);
1101
1311
  const row = result.rows[0];
1102
- return row ? this.mapRunRow(row) : null;
1312
+ return row ? mapRunRow2(row, this.mapperCtx) : null;
1103
1313
  }
1104
1314
  // ============================================================================
1105
- // Row Mapping
1315
+ // Row Mapping (adapter-only tables)
1106
1316
  // ============================================================================
1107
- mapRunRow(row) {
1108
- return {
1109
- id: row.id,
1110
- kind: row.kind,
1111
- status: row.status,
1112
- parentRunId: row.parent_run_id ?? void 0,
1113
- input: typeof row.input_json === "string" ? JSON.parse(row.input_json) : row.input_json,
1114
- context: typeof row.context_json === "string" ? JSON.parse(row.context_json) : row.context_json,
1115
- output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
1116
- error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
1117
- metadata: typeof row.metadata_json === "string" ? JSON.parse(row.metadata_json) : row.metadata_json,
1118
- priority: row.priority ?? 0,
1119
- timeoutMs: row.timeout_ms ?? void 0,
1120
- createdAt: new Date(row.created_at),
1121
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
1122
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
1123
- };
1124
- }
1125
- /**
1126
- * Map a database row to an extended workflow run record.
1127
- */
1128
- mapExtendedRunRow(row) {
1129
- return {
1130
- id: row.id,
1131
- kind: row.kind,
1132
- status: row.status,
1133
- input: typeof row.input_json === "string" ? JSON.parse(row.input_json) : row.input_json,
1134
- metadata: typeof row.metadata_json === "string" ? JSON.parse(row.metadata_json) : row.metadata_json,
1135
- context: typeof row.context_json === "string" ? JSON.parse(row.context_json) : row.context_json,
1136
- output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
1137
- error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
1138
- priority: row.priority ?? 0,
1139
- timeoutMs: row.timeout_ms ?? void 0,
1140
- createdAt: new Date(row.created_at),
1141
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
1142
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
1143
- };
1144
- }
1317
+ //
1318
+ // Mappers and JSON-parse helpers for the three core tables live in
1319
+ // `./postgres-core.ts`. The single mapper kept here covers
1320
+ // `stepflow_step_results`, the only table outside the core schema.
1145
1321
  mapStepResultRow(row) {
1322
+ const ctx = this.mapperCtx;
1146
1323
  return {
1147
1324
  id: row.id,
1148
1325
  runId: row.run_id,
1149
1326
  stepName: row.step_name,
1150
1327
  status: row.status,
1151
- output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
1152
- error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
1328
+ output: safeParseOptionalField2(row.output_json, {
1329
+ ...ctx,
1330
+ rowId: row.id,
1331
+ column: "output_json"
1332
+ }),
1333
+ error: safeParseOptionalField2(row.error_json, {
1334
+ ...ctx,
1335
+ rowId: row.id,
1336
+ column: "error_json"
1337
+ }),
1153
1338
  attempt: row.attempt,
1154
1339
  startedAt: row.started_at ? new Date(row.started_at) : void 0,
1155
1340
  completedAt: row.completed_at ? new Date(row.completed_at) : void 0
1156
1341
  };
1157
1342
  }
1158
- mapStepRow(row) {
1159
- return {
1160
- id: row.id,
1161
- runId: row.run_id,
1162
- stepKey: row.step_key,
1163
- stepName: row.step_name,
1164
- status: row.status,
1165
- attempt: row.attempt,
1166
- result: row.result_json ? typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json : void 0,
1167
- error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
1168
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
1169
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
1170
- };
1171
- }
1172
- mapEventRow(row) {
1173
- return {
1174
- id: row.id,
1175
- runId: row.run_id,
1176
- stepKey: row.step_key ?? void 0,
1177
- eventType: row.event_type,
1178
- level: row.level,
1179
- payload: row.payload_json ? typeof row.payload_json === "string" ? JSON.parse(row.payload_json) : row.payload_json : void 0,
1180
- timestamp: new Date(row.timestamp)
1181
- };
1182
- }
1183
1343
  // ============================================================================
1184
1344
  // New WorkflowStorage Methods
1185
1345
  // ============================================================================
@@ -1248,7 +1408,7 @@ var PostgresStorageAdapter = class {
1248
1408
  */
1249
1409
  async getStepResult(runId, stepName) {
1250
1410
  this.ensureInitialized();
1251
- const row = await this.qb.selectFrom("stepflow_step_results").selectAll().where("run_id", "=", runId).where("step_name", "=", stepName).executeTakeFirst();
1411
+ const row = await this.extQb.selectFrom("stepflow_step_results").selectAll().where("run_id", "=", runId).where("step_name", "=", stepName).executeTakeFirst();
1252
1412
  return row ? this.mapStepResultRow(row) : void 0;
1253
1413
  }
1254
1414
  /**
@@ -1256,7 +1416,7 @@ var PostgresStorageAdapter = class {
1256
1416
  */
1257
1417
  async getStepResults(runId) {
1258
1418
  this.ensureInitialized();
1259
- const rows = await this.qb.selectFrom("stepflow_step_results").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
1419
+ const rows = await this.extQb.selectFrom("stepflow_step_results").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
1260
1420
  return rows.map((row) => this.mapStepResultRow(row));
1261
1421
  }
1262
1422
  /**
@@ -1266,7 +1426,7 @@ var PostgresStorageAdapter = class {
1266
1426
  async saveStepResult(result) {
1267
1427
  this.ensureInitialized();
1268
1428
  const id = result.id ?? generateId();
1269
- await this.qb.insertInto("stepflow_step_results").values({
1429
+ await this.extQb.insertInto("stepflow_step_results").values({
1270
1430
  id,
1271
1431
  run_id: result.runId,
1272
1432
  step_name: result.stepName,
@@ -1307,187 +1467,24 @@ var PostgresStorageAdapter = class {
1307
1467
  };
1308
1468
  }
1309
1469
  };
1310
- var PostgresTransactionAdapter = class {
1311
- constructor(trx, schema) {
1312
- this.trx = trx;
1313
- this.schema = schema;
1314
- this.qb = trx.withSchema(schema);
1315
- }
1316
- qb;
1317
- async createRun(run) {
1318
- const id = "id" in run && run.id ? run.id : generateId();
1319
- const createdAt = /* @__PURE__ */ new Date();
1320
- await this.qb.insertInto("runs").values({
1321
- id,
1322
- kind: run.kind,
1323
- status: run.status,
1324
- parent_run_id: "parentRunId" in run ? run.parentRunId ?? null : null,
1325
- input_json: JSON.stringify(run.input),
1326
- metadata_json: JSON.stringify(run.metadata ?? {}),
1327
- context_json: JSON.stringify(run.context ?? {}),
1328
- output_json: null,
1329
- error_json: "error" in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
1330
- priority: "priority" in run ? run.priority ?? 0 : 0,
1331
- timeout_ms: "timeoutMs" in run ? run.timeoutMs ?? null : null,
1332
- created_at: createdAt,
1333
- started_at: null,
1334
- finished_at: null
1335
- }).execute();
1336
- return {
1337
- id,
1338
- kind: run.kind,
1339
- status: run.status,
1340
- parentRunId: "parentRunId" in run ? run.parentRunId : void 0,
1341
- input: run.input,
1342
- context: run.context ?? {},
1343
- output: void 0,
1344
- error: void 0,
1345
- metadata: run.metadata ?? {},
1346
- priority: "priority" in run ? run.priority ?? 0 : 0,
1347
- timeoutMs: "timeoutMs" in run ? run.timeoutMs : void 0,
1348
- createdAt
1349
- };
1350
- }
1351
- async getRun(runId) {
1352
- const row = await this.qb.selectFrom("runs").selectAll().where("id", "=", runId).executeTakeFirst();
1353
- return row ? this.mapRunRow(row) : null;
1354
- }
1355
- async updateRun(runId, updates) {
1356
- const updateData = {};
1357
- if (updates.status !== void 0) updateData.status = updates.status;
1358
- if (updates.context !== void 0) updateData.context_json = JSON.stringify(updates.context);
1359
- if ("output" in updates && updates.output !== void 0) updateData.output_json = JSON.stringify(updates.output);
1360
- if (updates.error !== void 0) updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
1361
- if (updates.startedAt !== void 0) updateData.started_at = updates.startedAt;
1362
- if (updates.finishedAt !== void 0) updateData.finished_at = updates.finishedAt;
1363
- if (Object.keys(updateData).length > 0) {
1364
- await this.qb.updateTable("runs").set(updateData).where("id", "=", runId).execute();
1365
- }
1366
- }
1367
- async listRuns(options = {}) {
1368
- const limit = options.limit ?? 50;
1369
- const offset = options.offset ?? 0;
1370
- let query = this.qb.selectFrom("runs").selectAll();
1371
- if (options.kind) query = query.where("kind", "=", options.kind);
1372
- if (options.status) {
1373
- const statuses = Array.isArray(options.status) ? options.status : [options.status];
1374
- query = query.where("status", "in", statuses);
1375
- }
1376
- if (options.parentRunId !== void 0) {
1377
- query = query.where("parent_run_id", "=", options.parentRunId);
1378
- }
1379
- const orderColumn = (options.orderBy ?? "createdAt") === "createdAt" ? "created_at" : options.orderBy === "startedAt" ? "started_at" : "finished_at";
1380
- const orderDirection = options.orderDirection ?? "desc";
1381
- query = query.orderBy(orderColumn, orderDirection).limit(limit).offset(offset);
1382
- const rows = await query.execute();
1383
- return {
1384
- items: rows.map((row) => this.mapRunRow(row)),
1385
- total: rows.length
1386
- // Simplified for transaction context
1387
- };
1388
- }
1389
- async createStep(step) {
1390
- const id = generateId();
1391
- await this.qb.insertInto("workflow_run_steps").values({
1392
- id,
1393
- run_id: step.runId,
1394
- step_key: step.stepKey,
1395
- step_name: step.stepName,
1396
- status: step.status,
1397
- attempt: step.attempt,
1398
- result_json: step.result !== void 0 ? JSON.stringify(step.result) : null,
1399
- error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
1400
- started_at: step.startedAt ?? null,
1401
- finished_at: step.finishedAt ?? null
1402
- }).execute();
1403
- return { ...step, id };
1404
- }
1405
- async getStep(stepId) {
1406
- const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("id", "=", stepId).executeTakeFirst();
1407
- return row ? this.mapStepRow(row) : null;
1408
- }
1409
- async updateStep(stepId, updates) {
1410
- const updateData = {};
1411
- if (updates.status !== void 0) updateData.status = updates.status;
1412
- if (updates.attempt !== void 0) updateData.attempt = updates.attempt;
1413
- if (updates.result !== void 0) updateData.result_json = JSON.stringify(updates.result);
1414
- if (updates.error !== void 0) updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
1415
- if (updates.finishedAt !== void 0) updateData.finished_at = updates.finishedAt;
1416
- if (Object.keys(updateData).length > 0) {
1417
- await this.qb.updateTable("workflow_run_steps").set(updateData).where("id", "=", stepId).execute();
1418
- }
1470
+ var PostgresTransactionAdapter = class extends PostgresStorageCore {
1471
+ _qb;
1472
+ constructor(trx, schema, logger) {
1473
+ super(schema, logger);
1474
+ this._qb = trx.withSchema(schema);
1419
1475
  }
1420
- async getStepsForRun(runId) {
1421
- const rows = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
1422
- return rows.map((row) => this.mapStepRow(row));
1423
- }
1424
- async saveEvent(event) {
1425
- const id = generateId();
1426
- await this.qb.insertInto("workflow_events").values({
1427
- id,
1428
- run_id: event.runId,
1429
- step_key: event.stepKey ?? null,
1430
- event_type: event.eventType,
1431
- level: event.level,
1432
- payload_json: event.payload !== void 0 ? JSON.stringify(event.payload) : null,
1433
- timestamp: event.timestamp
1434
- }).execute();
1435
- }
1436
- async getEventsForRun(runId, options = {}) {
1437
- let query = this.qb.selectFrom("workflow_events").selectAll().where("run_id", "=", runId);
1438
- if (options.stepKey) query = query.where("step_key", "=", options.stepKey);
1439
- if (options.level) query = query.where("level", "=", options.level);
1440
- const rows = await query.orderBy("timestamp", "asc").limit(options.limit ?? 1e3).offset(options.offset ?? 0).execute();
1441
- return rows.map((row) => this.mapEventRow(row));
1442
- }
1443
- mapRunRow(row) {
1444
- return {
1445
- id: row.id,
1446
- kind: row.kind,
1447
- status: row.status,
1448
- parentRunId: row.parent_run_id ?? void 0,
1449
- input: typeof row.input_json === "string" ? JSON.parse(row.input_json) : row.input_json,
1450
- context: typeof row.context_json === "string" ? JSON.parse(row.context_json) : row.context_json,
1451
- output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
1452
- error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
1453
- metadata: typeof row.metadata_json === "string" ? JSON.parse(row.metadata_json) : row.metadata_json,
1454
- priority: row.priority ?? 0,
1455
- timeoutMs: row.timeout_ms ?? void 0,
1456
- createdAt: new Date(row.created_at),
1457
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
1458
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
1459
- };
1460
- }
1461
- mapStepRow(row) {
1462
- return {
1463
- id: row.id,
1464
- runId: row.run_id,
1465
- stepKey: row.step_key,
1466
- stepName: row.step_name,
1467
- status: row.status,
1468
- attempt: row.attempt,
1469
- result: row.result_json ? typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json : void 0,
1470
- error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
1471
- startedAt: row.started_at ? new Date(row.started_at) : void 0,
1472
- finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
1473
- };
1476
+ get qb() {
1477
+ return this._qb;
1474
1478
  }
1475
- mapEventRow(row) {
1476
- return {
1477
- id: row.id,
1478
- runId: row.run_id,
1479
- stepKey: row.step_key ?? void 0,
1480
- eventType: row.event_type,
1481
- level: row.level,
1482
- payload: row.payload_json ? typeof row.payload_json === "string" ? JSON.parse(row.payload_json) : row.payload_json : void 0,
1483
- timestamp: new Date(row.timestamp)
1484
- };
1479
+ get warnLabel() {
1480
+ return "PostgresTransactionAdapter";
1485
1481
  }
1486
1482
  };
1487
1483
 
1488
1484
  export {
1489
1485
  MemoryStorageAdapter,
1490
1486
  SQLiteStorageAdapter,
1491
- PostgresStorageAdapter
1487
+ PostgresStorageAdapter,
1488
+ PostgresTransactionAdapter
1492
1489
  };
1493
- //# sourceMappingURL=chunk-4VOKMGP6.js.map
1490
+ //# sourceMappingURL=chunk-JUPZEBR3.js.map