@multiplier-labs/stepflow 0.3.3 → 0.3.4

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 (69) hide show
  1. package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +146 -0
  2. package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
  3. package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
  4. package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
  5. package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
  6. package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
  7. package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
  8. package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
  9. package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
  10. package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
  11. package/.github/workflows/publish.yml +6 -0
  12. package/dist/{chunk-4VOKMGP6.js → chunk-5XJBAPXF.js} +517 -522
  13. package/dist/chunk-5XJBAPXF.js.map +1 -0
  14. package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
  15. package/dist/chunk-FMPJBK2N.js.map +1 -0
  16. package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
  17. package/dist/chunk-JPXPB7U5.js.map +1 -0
  18. package/dist/{chunk-GYPNJBDP.js → chunk-U3PCVK6O.js} +266 -47
  19. package/dist/chunk-U3PCVK6O.js.map +1 -0
  20. package/dist/events/index.d.ts +56 -3
  21. package/dist/events/index.js +1 -1
  22. package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
  23. package/dist/index.d.ts +2 -12
  24. package/dist/index.js +764 -485
  25. package/dist/index.js.map +1 -1
  26. package/dist/scheduler/index.d.ts +1 -1
  27. package/dist/scheduler/index.js +2 -2
  28. package/dist/storage/index.d.ts +313 -47
  29. package/dist/storage/index.js +4 -2
  30. package/docs/stepflow-api-reference.md +3 -1
  31. package/package.json +7 -4
  32. package/src/core/engine/concurrency.ts +229 -0
  33. package/src/core/engine/lifecycle.ts +176 -0
  34. package/src/core/engine.test.ts +70 -0
  35. package/src/core/engine.ts +91 -185
  36. package/src/core/orchestrator/persistence.ts +165 -0
  37. package/src/core/orchestrator/plan-resolution.ts +140 -0
  38. package/src/core/orchestrator/state-transitions.ts +109 -0
  39. package/src/core/orchestrator/step-runner.ts +427 -0
  40. package/src/core/orchestrator.ts +40 -359
  41. package/src/events/webhook.test.ts +292 -0
  42. package/src/events/webhook.ts +320 -77
  43. package/src/planning/evaluate-condition.test.ts +153 -0
  44. package/src/planning/evaluate-condition.ts +105 -0
  45. package/src/planning/planner.ts +1 -83
  46. package/src/planning/registry.ts +3 -85
  47. package/src/scheduler/cron.test.ts +9 -1
  48. package/src/scheduler/cron.ts +26 -2
  49. package/src/scheduler/postgres-persistence.ts +27 -15
  50. package/src/scheduler/sqlite-persistence.ts +28 -3
  51. package/src/storage/postgres-core.ts +550 -0
  52. package/src/storage/postgres-migration.test.ts +69 -0
  53. package/src/storage/postgres-transaction-pagination.test.ts +107 -0
  54. package/src/storage/postgres.ts +171 -690
  55. package/src/storage/sqlite-core.ts +151 -0
  56. package/src/storage/sqlite.test.ts +177 -1
  57. package/src/storage/sqlite.ts +183 -93
  58. package/src/utils/index.ts +2 -0
  59. package/src/utils/pinned-fetch.test.ts +84 -0
  60. package/src/utils/pinned-fetch.ts +118 -0
  61. package/src/utils/postgres-deps.ts +16 -0
  62. package/src/utils/safe-json.test.ts +166 -0
  63. package/src/utils/safe-json.ts +107 -0
  64. package/src/utils/webhook-signature.test.ts +111 -0
  65. package/src/utils/webhook-signature.ts +74 -0
  66. package/dist/chunk-4VOKMGP6.js.map +0 -1
  67. package/dist/chunk-GYPNJBDP.js.map +0 -1
  68. package/dist/chunk-KHTUWLVZ.js.map +0 -1
  69. package/dist/chunk-ML35PIHX.js.map +0 -1
@@ -1,12 +1,34 @@
1
1
  /**
2
2
  * PostgreSQL storage adapter using Kysely.
3
3
  *
4
- * Provides durable persistence for workflow runs, steps, and events
5
- * with support for distributed deployments and connection pooling.
4
+ * Provides durable persistence for workflow runs, steps, and events with
5
+ * support for distributed deployments and connection pooling.
6
+ *
7
+ * ## Module layout
8
+ *
9
+ * - `PostgresStorageAdapter` (this file) owns the `pg.Pool` / Kysely
10
+ * instance, runs schema migrations on `initialize()`, and implements all
11
+ * non-CRUD methods: connection lifecycle (`close`, `transaction`),
12
+ * workflow-level operations (`dequeueRun`, `cleanupStaleRuns`,
13
+ * `markRunsAsFailed`, `getStats`, `deleteRun`, `deleteOldRuns`,
14
+ * `getInterruptedRuns`, `getLastCompletedStep`), and the
15
+ * `stepflow_step_results` accessors.
16
+ *
17
+ * - `PostgresTransactionAdapter` (this file) is a thin shell used by
18
+ * `PostgresStorageAdapter.transaction()`. It binds a transaction-scoped
19
+ * Kysely query builder; everything else comes from the shared core.
20
+ * Exported only for regression tests.
21
+ *
22
+ * - `PostgresStorageCore` (./postgres-core.ts) holds the shared CRUD
23
+ * methods (`createRun`, `getRun`, `updateRun`, `listRuns`, `createStep`,
24
+ * `getStep`, `updateStep`, `getStepsForRun`, `saveEvent`,
25
+ * `getEventsForRun`), the row mappers, and the JSON-parse helpers. Both
26
+ * classes here extend it.
6
27
  */
7
28
 
8
29
  import type { Kysely as KyselyType } from 'kysely';
9
30
  import type { Pool, PoolConfig } from 'pg';
31
+ import type { Logger } from '../core/types.js';
10
32
  import { loadPostgresDeps } from '../utils/postgres-deps.js';
11
33
 
12
34
  // Lazy-loaded dependencies - populated by loadPostgresDeps() during initialize()
@@ -19,19 +41,20 @@ import type {
19
41
  StorageAdapter,
20
42
  WorkflowRunRecord,
21
43
  WorkflowRunStepRecord,
22
- WorkflowEventRecord,
23
- ListRunsOptions,
24
- ListEventsOptions,
25
- PaginatedResult,
26
- CreateRunInput,
27
- UpdateRunInput,
28
44
  StepResult,
29
- ExtendedWorkflowRunRecord,
30
- ExtendedRunStatus,
31
45
  ExtendedStepStatus,
32
46
  } from './types.js';
33
- import type { RunStatus, StepStatus, WorkflowError } from '../core/types.js';
34
- import { sanitizeErrorForStorage } from '../utils/logger.js';
47
+ import {
48
+ PostgresStorageCore,
49
+ mapRunRow,
50
+ mapStepRow,
51
+ safeParseOptionalField,
52
+ type CoreSchemaQueryBuilder,
53
+ type PostgresCoreDatabase,
54
+ type SchemaQueryBuilder,
55
+ type WorkflowRunsTable,
56
+ type WorkflowRunStepsTable,
57
+ } from './postgres-core.js';
35
58
 
36
59
  /** Strip `stack` from a generic error record before persistence. */
37
60
  function stripStack<T extends Record<string, unknown>>(error: T): Omit<T, 'stack'> {
@@ -42,55 +65,11 @@ function stripStack<T extends Record<string, unknown>>(error: T): Omit<T, 'stack
42
65
  // ============================================================================
43
66
  // Database Types (Kysely schema)
44
67
  // ============================================================================
45
-
46
- /** Kysely row type for the workflow_runs table. */
47
- interface WorkflowRunsTable {
48
- id: string;
49
- /** Workflow kind identifier (e.g. 'order-processing'). */
50
- kind: string;
51
- /** Current run status (maps to RunStatus). */
52
- status: string;
53
- /** Parent run ID for child workflows, null for top-level runs. */
54
- parent_run_id: string | null;
55
- /** JSON-serialized input payload. */
56
- input_json: string;
57
- /** JSON-serialized arbitrary metadata. */
58
- metadata_json: string;
59
- /** JSON-serialized workflow context (accumulated step results). */
60
- context_json: string;
61
- /** JSON-serialized output payload, null until run completes. */
62
- output_json: string | null;
63
- /** JSON-serialized error object, null unless run failed. */
64
- error_json: string | null;
65
- /** Execution priority (lower number = higher precedence). */
66
- priority: number;
67
- /** Workflow-level timeout in milliseconds, null for no timeout. */
68
- timeout_ms: number | null;
69
- created_at: Date;
70
- started_at: Date | null;
71
- finished_at: Date | null;
72
- }
73
-
74
- /** Kysely row type for the workflow_run_steps table. */
75
- interface WorkflowRunStepsTable {
76
- id: string;
77
- /** Foreign key to workflow_runs.id. */
78
- run_id: string;
79
- /** Unique step identifier within the workflow definition. */
80
- step_key: string;
81
- /** Human-readable step name. */
82
- step_name: string;
83
- /** Current step status (maps to StepStatus). */
84
- status: string;
85
- /** Current retry attempt number (1-based). */
86
- attempt: number;
87
- /** JSON-serialized step result, null until step completes. */
88
- result_json: string | null;
89
- /** JSON-serialized error object, null unless step failed. */
90
- error_json: string | null;
91
- started_at: Date | null;
92
- finished_at: Date | null;
93
- }
68
+ //
69
+ // The `runs`, `workflow_run_steps`, and `workflow_events` table shapes live
70
+ // in `./postgres-core.ts` so the shared CRUD core can target them without
71
+ // re-declaration. The full `StepflowDatabase` schema below adds the
72
+ // adapter-only `stepflow_step_results` table (extended storage).
94
73
 
95
74
  /** Kysely row type for the stepflow_step_results table (extended storage). */
96
75
  interface StepflowStepResultsTable {
@@ -111,26 +90,7 @@ interface StepflowStepResultsTable {
111
90
  completed_at: Date | null;
112
91
  }
113
92
 
114
- /** Kysely row type for the workflow_events table. */
115
- interface WorkflowEventsTable {
116
- id: string;
117
- /** Foreign key to workflow_runs.id. */
118
- run_id: string;
119
- /** Step key that emitted this event, null for run-level events. */
120
- step_key: string | null;
121
- /** Event type identifier (e.g. 'run.started', 'step.completed'). */
122
- event_type: string;
123
- /** Severity level: 'info', 'warn', or 'error'. */
124
- level: string;
125
- /** JSON-serialized event payload, null if no extra data. */
126
- payload_json: string | null;
127
- timestamp: Date;
128
- }
129
-
130
- interface StepflowDatabase {
131
- runs: WorkflowRunsTable;
132
- workflow_run_steps: WorkflowRunStepsTable;
133
- workflow_events: WorkflowEventsTable;
93
+ interface StepflowDatabase extends PostgresCoreDatabase {
134
94
  stepflow_step_results: StepflowStepResultsTable;
135
95
  }
136
96
 
@@ -170,6 +130,13 @@ export interface PostgresStorageConfig {
170
130
  * @default true
171
131
  */
172
132
  autoMigrate?: boolean;
133
+
134
+ /**
135
+ * Optional structured logger used to surface JSON corruption events.
136
+ * Without one, parse failures still increment the corruption counter
137
+ * exposed via `getJsonParseCorruptionCount()` but produce no log output.
138
+ */
139
+ logger?: Logger;
173
140
  }
174
141
 
175
142
  // ============================================================================
@@ -214,35 +181,55 @@ export interface PostgresStorageConfig {
214
181
  * const storage = new PostgresStorageAdapter({ pool });
215
182
  * ```
216
183
  */
217
- export class PostgresStorageAdapter implements StorageAdapter {
184
+ export class PostgresStorageAdapter extends PostgresStorageCore {
218
185
  private db!: KyselyType<StepflowDatabase>;
219
186
  private pool!: Pool;
220
187
  private ownsPool = false;
221
- private schema: string;
222
188
  private autoMigrate: boolean;
223
189
  private initialized = false;
224
190
  private config: PostgresStorageConfig;
225
191
 
226
192
  constructor(config: PostgresStorageConfig) {
227
- this.schema = config.schema ?? 'public';
228
- if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {
193
+ const schema = config.schema ?? 'public';
194
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(schema)) {
229
195
  throw new Error(
230
- `Invalid schema name "${this.schema}". Schema must start with a letter or underscore, ` +
196
+ `Invalid schema name "${schema}". Schema must start with a letter or underscore, ` +
231
197
  `contain only alphanumeric characters and underscores, and be at most 63 characters.`
232
198
  );
233
199
  }
200
+ super(schema, config.logger);
234
201
  this.autoMigrate = config.autoMigrate !== false;
235
202
  this.config = config;
236
203
  }
237
204
 
238
205
  /**
239
- * Get a schema-scoped query builder.
240
- * All queries MUST use this instead of this.db directly to respect config.schema.
206
+ * Core-tables query builder consumed by `PostgresStorageCore`. Returns the
207
+ * schema-scoped Kysely view typed against the three core tables, which is
208
+ * what the shared CRUD methods need.
241
209
  */
242
- private get qb() {
210
+ protected override get qb(): CoreSchemaQueryBuilder {
211
+ return this.db.withSchema(this.schema) as unknown as CoreSchemaQueryBuilder;
212
+ }
213
+
214
+ /**
215
+ * Extended query builder for adapter-only tables (currently
216
+ * `stepflow_step_results`). Kept separate from `qb` so the base class stays
217
+ * typed against the core schema.
218
+ */
219
+ private get extQb(): SchemaQueryBuilder<StepflowDatabase> {
243
220
  return this.db.withSchema(this.schema);
244
221
  }
245
222
 
223
+ /** Tag used by shared row mappers in `console.warn` messages. */
224
+ protected override get warnLabel(): string {
225
+ return 'PostgresStorageAdapter';
226
+ }
227
+
228
+ /** Override of `PostgresStorageCore.ensureReady()` — enforces `initialize()`. */
229
+ protected override ensureReady(): void {
230
+ this.ensureInitialized();
231
+ }
232
+
246
233
  private ensureInitialized(): void {
247
234
  if (!this.initialized) {
248
235
  throw new Error(
@@ -308,6 +295,47 @@ export class PostgresStorageAdapter implements StorageAdapter {
308
295
  // Schema Creation
309
296
  // ============================================================================
310
297
 
298
+ /**
299
+ * Run a single migration step. Tolerates only known-benign Postgres errors
300
+ * (duplicate object codes that map to "already applied"), and rethrows
301
+ * everything else with a clear message so partial-state upgrades are not
302
+ * silently swallowed.
303
+ */
304
+ protected async runMigration(label: string, fn: () => Promise<unknown>): Promise<void> {
305
+ try {
306
+ await fn();
307
+ } catch (err) {
308
+ if (PostgresStorageAdapter.isBenignMigrationError(err)) {
309
+ return;
310
+ }
311
+ const message = err instanceof Error ? err.message : String(err);
312
+ throw new Error(`Schema migration failed (${label}): ${message}`, { cause: err });
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Postgres SQLSTATE codes that indicate a migration step has already been
318
+ * applied (object already exists). These are safe to ignore on idempotent
319
+ * re-runs; everything else must propagate.
320
+ *
321
+ * - 42701: duplicate_column
322
+ * - 42P07: duplicate_table
323
+ * - 42P06: duplicate_schema
324
+ * - 42710: duplicate_object (e.g., index, constraint)
325
+ */
326
+ private static readonly BENIGN_MIGRATION_CODES = new Set([
327
+ '42701',
328
+ '42P07',
329
+ '42P06',
330
+ '42710',
331
+ ]);
332
+
333
+ private static isBenignMigrationError(err: unknown): boolean {
334
+ if (!err || typeof err !== 'object') return false;
335
+ const code = (err as { code?: unknown }).code;
336
+ return typeof code === 'string' && PostgresStorageAdapter.BENIGN_MIGRATION_CODES.has(code);
337
+ }
338
+
311
339
  /**
312
340
  * Create the workflow tables if they don't exist.
313
341
  */
@@ -341,14 +369,15 @@ export class PostgresStorageAdapter implements StorageAdapter {
341
369
  `.execute(this.db);
342
370
 
343
371
  // Add new columns if they don't exist (for migrations)
344
- await sql`
345
- ALTER TABLE ${sql.table(`${this.schema}.runs`)}
346
- ADD COLUMN IF NOT EXISTS output_json JSONB,
347
- ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,
348
- ADD COLUMN IF NOT EXISTS timeout_ms INTEGER
349
- `.execute(this.db).catch(() => {
350
- // Ignore if columns already exist or syntax not supported
351
- });
372
+ await this.runMigration(
373
+ `add columns to ${this.schema}.runs`,
374
+ () => sql`
375
+ ALTER TABLE ${sql.table(`${this.schema}.runs`)}
376
+ ADD COLUMN IF NOT EXISTS output_json JSONB,
377
+ ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,
378
+ ADD COLUMN IF NOT EXISTS timeout_ms INTEGER
379
+ `.execute(this.db)
380
+ );
352
381
 
353
382
  // Create indexes for runs
354
383
  await sql`
@@ -465,287 +494,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
465
494
  }
466
495
 
467
496
  // ============================================================================
468
- // Run Operations
469
- // ============================================================================
470
-
471
- /**
472
- * Create a new workflow run.
473
- * Supports both legacy and new CreateRunInput interfaces.
474
- */
475
- async createRun(run: CreateRunInput | Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {
476
- this.ensureInitialized();
477
- const id = 'id' in run && run.id ? run.id : generateId();
478
- const createdAt = new Date();
479
-
480
- await this.qb
481
- .insertInto('runs')
482
- .values({
483
- id,
484
- kind: run.kind,
485
- status: run.status,
486
- parent_run_id: 'parentRunId' in run ? (run.parentRunId ?? null) : null,
487
- input_json: JSON.stringify(run.input),
488
- metadata_json: JSON.stringify(run.metadata ?? {}),
489
- context_json: JSON.stringify(run.context ?? {}), // Default to empty object
490
- output_json: null,
491
- error_json: 'error' in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
492
- priority: 'priority' in run ? (run.priority ?? 0) : 0,
493
- timeout_ms: 'timeoutMs' in run ? (run.timeoutMs ?? null) : null,
494
- created_at: createdAt,
495
- started_at: null,
496
- finished_at: null,
497
- })
498
- .execute();
499
-
500
- return {
501
- id,
502
- kind: run.kind,
503
- status: run.status as RunStatus,
504
- parentRunId: 'parentRunId' in run ? run.parentRunId : undefined,
505
- input: run.input,
506
- context: run.context ?? {},
507
- output: undefined,
508
- error: undefined,
509
- metadata: run.metadata ?? {},
510
- priority: 'priority' in run ? (run.priority ?? 0) : 0,
511
- timeoutMs: 'timeoutMs' in run ? run.timeoutMs : undefined,
512
- createdAt,
513
- };
514
- }
515
-
516
- async getRun(runId: string): Promise<WorkflowRunRecord | null> {
517
- this.ensureInitialized();
518
- const row = await this.qb
519
- .selectFrom('runs')
520
- .selectAll()
521
- .where('id', '=', runId)
522
- .executeTakeFirst();
523
-
524
- return row ? this.mapRunRow(row) : null;
525
- }
526
-
527
- /**
528
- * Update a workflow run.
529
- * Supports both legacy Partial<WorkflowRunRecord> and new UpdateRunInput interfaces.
530
- */
531
- async updateRun(runId: string, updates: UpdateRunInput | Partial<WorkflowRunRecord>): Promise<void> {
532
- this.ensureInitialized();
533
- const updateData: Partial<WorkflowRunsTable> = {};
534
-
535
- if (updates.status !== undefined) {
536
- updateData.status = updates.status;
537
- }
538
- if (updates.context !== undefined) {
539
- updateData.context_json = JSON.stringify(updates.context);
540
- }
541
- if ('output' in updates && updates.output !== undefined) {
542
- updateData.output_json = JSON.stringify(updates.output);
543
- }
544
- if (updates.error !== undefined) {
545
- updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
546
- }
547
- if (updates.startedAt !== undefined) {
548
- updateData.started_at = updates.startedAt;
549
- }
550
- if (updates.finishedAt !== undefined) {
551
- updateData.finished_at = updates.finishedAt;
552
- }
553
-
554
- if (Object.keys(updateData).length > 0) {
555
- await this.qb
556
- .updateTable('runs')
557
- .set(updateData)
558
- .where('id', '=', runId)
559
- .execute();
560
- }
561
- }
562
-
563
- /**
564
- * List workflow runs with filtering and pagination.
565
- */
566
- /**
567
- * Apply common run filters to a Kysely query builder.
568
- * Used by both the data query and count query in listRuns to avoid duplication.
569
- */
570
- private applyRunsFilters<T extends { where(col: any, op: any, val: any): T }>(
571
- query: T,
572
- options: ListRunsOptions
573
- ): T {
574
- if (options.kind) {
575
- query = query.where('kind', '=', options.kind);
576
- }
577
- if (options.status) {
578
- const statuses = Array.isArray(options.status) ? options.status : [options.status];
579
- query = query.where('status', 'in', statuses);
580
- }
581
- if (options.parentRunId !== undefined) {
582
- query = query.where('parent_run_id', '=', options.parentRunId);
583
- }
584
- return query;
585
- }
586
-
587
- async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {
588
- this.ensureInitialized();
589
- const limit = options.limit ?? 50;
590
- const offset = options.offset ?? 0;
591
-
592
- let query = this.applyRunsFilters(
593
- this.qb.selectFrom('runs').selectAll(),
594
- options
595
- );
596
-
597
- const countQuery = this.applyRunsFilters(
598
- this.qb.selectFrom('runs').select(sql<number>`count(*)`.as('count')),
599
- options
600
- );
601
-
602
- const countResult = await countQuery.executeTakeFirst() as { count?: string | number } | undefined;
603
- const total = Number(countResult?.count ?? 0);
604
-
605
- // Order
606
- const orderBy = options.orderBy ?? 'createdAt';
607
- const orderDirection = options.orderDirection ?? 'desc';
608
- const orderColumn = orderBy === 'createdAt' ? 'created_at' :
609
- orderBy === 'startedAt' ? 'started_at' : 'finished_at';
610
-
611
- query = query.orderBy(orderColumn, orderDirection);
612
-
613
- // Apply pagination
614
- query = query.limit(limit).offset(offset);
615
-
616
- const rows = await query.execute();
617
-
618
- return {
619
- items: rows.map(row => this.mapRunRow(row)),
620
- total,
621
- };
622
- }
623
-
624
- // ============================================================================
625
- // Step Operations
626
- // ============================================================================
627
-
628
- async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {
629
- this.ensureInitialized();
630
- const id = generateId();
631
-
632
- await this.qb
633
- .insertInto('workflow_run_steps')
634
- .values({
635
- id,
636
- run_id: step.runId,
637
- step_key: step.stepKey,
638
- step_name: step.stepName,
639
- status: step.status,
640
- attempt: step.attempt,
641
- result_json: step.result !== undefined ? JSON.stringify(step.result) : null,
642
- error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
643
- started_at: step.startedAt ?? null,
644
- finished_at: step.finishedAt ?? null,
645
- })
646
- .execute();
647
-
648
- return { ...step, id };
649
- }
650
-
651
- async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {
652
- this.ensureInitialized();
653
- const row = await this.qb
654
- .selectFrom('workflow_run_steps')
655
- .selectAll()
656
- .where('id', '=', stepId)
657
- .executeTakeFirst();
658
-
659
- return row ? this.mapStepRow(row) : null;
660
- }
661
-
662
- async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {
663
- this.ensureInitialized();
664
- const updateData: Partial<WorkflowRunStepsTable> = {};
665
-
666
- if (updates.status !== undefined) {
667
- updateData.status = updates.status;
668
- }
669
- if (updates.attempt !== undefined) {
670
- updateData.attempt = updates.attempt;
671
- }
672
- if (updates.result !== undefined) {
673
- updateData.result_json = JSON.stringify(updates.result);
674
- }
675
- if (updates.error !== undefined) {
676
- updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
677
- }
678
- if (updates.finishedAt !== undefined) {
679
- updateData.finished_at = updates.finishedAt;
680
- }
681
-
682
- if (Object.keys(updateData).length > 0) {
683
- await this.qb
684
- .updateTable('workflow_run_steps')
685
- .set(updateData)
686
- .where('id', '=', stepId)
687
- .execute();
688
- }
689
- }
690
-
691
- async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {
692
- this.ensureInitialized();
693
- const rows = await this.qb
694
- .selectFrom('workflow_run_steps')
695
- .selectAll()
696
- .where('run_id', '=', runId)
697
- .orderBy('started_at', 'asc')
698
- .execute();
699
-
700
- return rows.map(row => this.mapStepRow(row));
701
- }
702
-
703
- // ============================================================================
704
- // Event Operations
497
+ // Run / Step / Event CRUD
705
498
  // ============================================================================
706
-
707
- async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {
708
- this.ensureInitialized();
709
- const id = generateId();
710
-
711
- await this.qb
712
- .insertInto('workflow_events')
713
- .values({
714
- id,
715
- run_id: event.runId,
716
- step_key: event.stepKey ?? null,
717
- event_type: event.eventType,
718
- level: event.level,
719
- payload_json: event.payload !== undefined ? JSON.stringify(event.payload) : null,
720
- timestamp: event.timestamp,
721
- })
722
- .execute();
723
- }
724
-
725
- async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {
726
- this.ensureInitialized();
727
- const limit = options.limit ?? 1000;
728
- const offset = options.offset ?? 0;
729
-
730
- let query = this.qb
731
- .selectFrom('workflow_events')
732
- .selectAll()
733
- .where('run_id', '=', runId);
734
-
735
- if (options.stepKey) {
736
- query = query.where('step_key', '=', options.stepKey);
737
- }
738
-
739
- if (options.level) {
740
- query = query.where('level', '=', options.level);
741
- }
742
-
743
- query = query.orderBy('timestamp', 'asc').limit(limit).offset(offset);
744
-
745
- const rows = await query.execute();
746
-
747
- return rows.map(row => this.mapEventRow(row));
748
- }
499
+ //
500
+ // The CRUD methods (`createRun`, `getRun`, `updateRun`, `listRuns`,
501
+ // `createStep`, `getStep`, `updateStep`, `getStepsForRun`, `saveEvent`,
502
+ // `getEventsForRun`) are inherited from `PostgresStorageCore`. Each call
503
+ // routes through `this.qb` (overridden above) and `this.ensureReady()`
504
+ // which calls `this.ensureInitialized()`.
749
505
 
750
506
  // ============================================================================
751
507
  // Transaction Support
@@ -758,7 +514,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
758
514
  this.ensureInitialized();
759
515
  return await this.db.transaction().execute(async (trx) => {
760
516
  // Create a transactional adapter wrapper
761
- const txAdapter = new PostgresTransactionAdapter(trx, this.schema);
517
+ const txAdapter = new PostgresTransactionAdapter(trx, this.schema, this.logger);
762
518
  return await fn(txAdapter);
763
519
  });
764
520
  }
@@ -798,7 +554,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
798
554
  .orderBy('created_at', 'asc')
799
555
  .execute();
800
556
 
801
- return rows.map(row => this.mapRunRow(row));
557
+ return rows.map(row => mapRunRow(row as WorkflowRunsTable, this.mapperCtx));
802
558
  }
803
559
 
804
560
  /**
@@ -816,7 +572,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
816
572
  .limit(1)
817
573
  .executeTakeFirst();
818
574
 
819
- return row ? this.mapStepRow(row) : null;
575
+ return row ? mapStepRow(row as WorkflowRunStepsTable, this.mapperCtx) : null;
820
576
  }
821
577
 
822
578
  // ============================================================================
@@ -851,94 +607,40 @@ export class PostgresStorageAdapter implements StorageAdapter {
851
607
  `.execute(this.db);
852
608
 
853
609
  const row = result.rows[0];
854
- return row ? this.mapRunRow(row) : null;
610
+ return row ? mapRunRow(row, this.mapperCtx) : null;
855
611
  }
856
612
 
857
613
  // ============================================================================
858
- // Row Mapping
614
+ // Row Mapping (adapter-only tables)
859
615
  // ============================================================================
860
-
861
- private mapRunRow(row: WorkflowRunsTable): WorkflowRunRecord {
862
- return {
863
- id: row.id,
864
- kind: row.kind,
865
- status: row.status as RunStatus,
866
- parentRunId: row.parent_run_id ?? undefined,
867
- input: typeof row.input_json === 'string' ? JSON.parse(row.input_json) : row.input_json,
868
- context: typeof row.context_json === 'string' ? JSON.parse(row.context_json) : row.context_json,
869
- output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
870
- error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
871
- metadata: typeof row.metadata_json === 'string' ? JSON.parse(row.metadata_json) : row.metadata_json,
872
- priority: row.priority ?? 0,
873
- timeoutMs: row.timeout_ms ?? undefined,
874
- createdAt: new Date(row.created_at),
875
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
876
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
877
- };
878
- }
879
-
880
- /**
881
- * Map a database row to an extended workflow run record.
882
- */
883
- private mapExtendedRunRow(row: WorkflowRunsTable): ExtendedWorkflowRunRecord {
884
- return {
885
- id: row.id,
886
- kind: row.kind,
887
- status: row.status as ExtendedRunStatus,
888
- input: typeof row.input_json === 'string' ? JSON.parse(row.input_json) : row.input_json,
889
- metadata: typeof row.metadata_json === 'string' ? JSON.parse(row.metadata_json) : row.metadata_json,
890
- context: typeof row.context_json === 'string' ? JSON.parse(row.context_json) : row.context_json,
891
- output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
892
- error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
893
- priority: row.priority ?? 0,
894
- timeoutMs: row.timeout_ms ?? undefined,
895
- createdAt: new Date(row.created_at),
896
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
897
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
898
- };
899
- }
616
+ //
617
+ // Mappers and JSON-parse helpers for the three core tables live in
618
+ // `./postgres-core.ts`. The single mapper kept here covers
619
+ // `stepflow_step_results`, the only table outside the core schema.
900
620
 
901
621
  private mapStepResultRow(row: StepflowStepResultsTable): StepResult {
622
+ const ctx = this.mapperCtx;
902
623
  return {
903
624
  id: row.id,
904
625
  runId: row.run_id,
905
626
  stepName: row.step_name,
906
627
  status: row.status as ExtendedStepStatus,
907
- output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
908
- error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
628
+ output: safeParseOptionalField(row.output_json, {
629
+ ...ctx,
630
+ rowId: row.id,
631
+ column: 'output_json',
632
+ }) as Record<string, unknown> | undefined,
633
+ error: safeParseOptionalField(row.error_json, {
634
+ ...ctx,
635
+ rowId: row.id,
636
+ column: 'error_json',
637
+ }) as Record<string, unknown> | undefined,
909
638
  attempt: row.attempt,
910
639
  startedAt: row.started_at ? new Date(row.started_at) : undefined,
911
640
  completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
912
641
  };
913
642
  }
914
643
 
915
- private mapStepRow(row: WorkflowRunStepsTable): WorkflowRunStepRecord {
916
- return {
917
- id: row.id,
918
- runId: row.run_id,
919
- stepKey: row.step_key,
920
- stepName: row.step_name,
921
- status: row.status as StepStatus,
922
- attempt: row.attempt,
923
- result: row.result_json ? (typeof row.result_json === 'string' ? JSON.parse(row.result_json) : row.result_json) : undefined,
924
- error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
925
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
926
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
927
- };
928
- }
929
-
930
- private mapEventRow(row: WorkflowEventsTable): WorkflowEventRecord {
931
- return {
932
- id: row.id,
933
- runId: row.run_id,
934
- stepKey: row.step_key ?? undefined,
935
- eventType: row.event_type,
936
- level: row.level as 'info' | 'warn' | 'error',
937
- payload: row.payload_json ? (typeof row.payload_json === 'string' ? JSON.parse(row.payload_json) : row.payload_json) : undefined,
938
- timestamp: new Date(row.timestamp),
939
- };
940
- }
941
-
942
644
  // ============================================================================
943
645
  // New WorkflowStorage Methods
944
646
  // ============================================================================
@@ -1016,7 +718,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
1016
718
  */
1017
719
  async getStepResult(runId: string, stepName: string): Promise<StepResult | undefined> {
1018
720
  this.ensureInitialized();
1019
- const row = await this.qb
721
+ const row = await this.extQb
1020
722
  .selectFrom('stepflow_step_results')
1021
723
  .selectAll()
1022
724
  .where('run_id', '=', runId)
@@ -1031,7 +733,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
1031
733
  */
1032
734
  async getStepResults(runId: string): Promise<StepResult[]> {
1033
735
  this.ensureInitialized();
1034
- const rows = await this.qb
736
+ const rows = await this.extQb
1035
737
  .selectFrom('stepflow_step_results')
1036
738
  .selectAll()
1037
739
  .where('run_id', '=', runId)
@@ -1049,7 +751,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
1049
751
  this.ensureInitialized();
1050
752
  const id = result.id ?? generateId();
1051
753
 
1052
- await this.qb
754
+ await this.extQb
1053
755
  .insertInto('stepflow_step_results')
1054
756
  .values({
1055
757
  id,
@@ -1104,248 +806,27 @@ export class PostgresStorageAdapter implements StorageAdapter {
1104
806
 
1105
807
  /**
1106
808
  * A StorageAdapter wrapper for use within transactions.
1107
- * This is used internally by PostgresStorageAdapter.transaction().
809
+ *
810
+ * Used internally by `PostgresStorageAdapter.transaction()`. All CRUD methods
811
+ * are inherited from `PostgresStorageCore`; this class only binds the
812
+ * transaction-scoped query builder.
813
+ *
814
+ * Exported for regression testing; not part of the public API.
1108
815
  */
1109
- class PostgresTransactionAdapter implements StorageAdapter {
1110
- private qb: ReturnType<KyselyType<StepflowDatabase>['withSchema']>;
1111
-
1112
- constructor(
1113
- private trx: KyselyType<StepflowDatabase>,
1114
- private schema: string
1115
- ) {
1116
- this.qb = trx.withSchema(schema);
1117
- }
1118
-
1119
- async createRun(run: CreateRunInput | Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {
1120
- const id = 'id' in run && run.id ? run.id : generateId();
1121
- const createdAt = new Date();
1122
-
1123
- await this.qb
1124
- .insertInto('runs')
1125
- .values({
1126
- id,
1127
- kind: run.kind,
1128
- status: run.status,
1129
- parent_run_id: 'parentRunId' in run ? (run.parentRunId ?? null) : null,
1130
- input_json: JSON.stringify(run.input),
1131
- metadata_json: JSON.stringify(run.metadata ?? {}),
1132
- context_json: JSON.stringify(run.context ?? {}),
1133
- output_json: null,
1134
- error_json: 'error' in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
1135
- priority: 'priority' in run ? (run.priority ?? 0) : 0,
1136
- timeout_ms: 'timeoutMs' in run ? (run.timeoutMs ?? null) : null,
1137
- created_at: createdAt,
1138
- started_at: null,
1139
- finished_at: null,
1140
- })
1141
- .execute();
1142
-
1143
- return {
1144
- id,
1145
- kind: run.kind,
1146
- status: run.status as RunStatus,
1147
- parentRunId: 'parentRunId' in run ? run.parentRunId : undefined,
1148
- input: run.input,
1149
- context: run.context ?? {},
1150
- output: undefined,
1151
- error: undefined,
1152
- metadata: run.metadata ?? {},
1153
- priority: 'priority' in run ? (run.priority ?? 0) : 0,
1154
- timeoutMs: 'timeoutMs' in run ? run.timeoutMs : undefined,
1155
- createdAt,
1156
- };
1157
- }
1158
-
1159
- async getRun(runId: string): Promise<WorkflowRunRecord | null> {
1160
- const row = await this.qb
1161
- .selectFrom('runs')
1162
- .selectAll()
1163
- .where('id', '=', runId)
1164
- .executeTakeFirst();
1165
-
1166
- return row ? this.mapRunRow(row) : null;
1167
- }
816
+ export class PostgresTransactionAdapter extends PostgresStorageCore {
817
+ private readonly _qb: CoreSchemaQueryBuilder;
1168
818
 
1169
- async updateRun(runId: string, updates: UpdateRunInput | Partial<WorkflowRunRecord>): Promise<void> {
1170
- const updateData: Partial<WorkflowRunsTable> = {};
1171
-
1172
- if (updates.status !== undefined) updateData.status = updates.status;
1173
- if (updates.context !== undefined) updateData.context_json = JSON.stringify(updates.context);
1174
- if ('output' in updates && updates.output !== undefined) updateData.output_json = JSON.stringify(updates.output);
1175
- if (updates.error !== undefined) updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error as WorkflowError));
1176
- if (updates.startedAt !== undefined) updateData.started_at = updates.startedAt;
1177
- if (updates.finishedAt !== undefined) updateData.finished_at = updates.finishedAt;
1178
-
1179
- if (Object.keys(updateData).length > 0) {
1180
- await this.qb.updateTable('runs').set(updateData).where('id', '=', runId).execute();
1181
- }
819
+ constructor(trx: KyselyType<StepflowDatabase>, schema: string, logger?: Logger) {
820
+ super(schema, logger);
821
+ this._qb = trx.withSchema(schema) as unknown as CoreSchemaQueryBuilder;
1182
822
  }
1183
823
 
1184
- async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {
1185
- const limit = options.limit ?? 50;
1186
- const offset = options.offset ?? 0;
1187
-
1188
- let query = this.qb.selectFrom('runs').selectAll();
1189
-
1190
- if (options.kind) query = query.where('kind', '=', options.kind);
1191
- if (options.status) {
1192
- const statuses = Array.isArray(options.status) ? options.status : [options.status];
1193
- query = query.where('status', 'in', statuses);
1194
- }
1195
- if (options.parentRunId !== undefined) {
1196
- query = query.where('parent_run_id', '=', options.parentRunId);
1197
- }
1198
-
1199
- const orderColumn = (options.orderBy ?? 'createdAt') === 'createdAt' ? 'created_at' :
1200
- options.orderBy === 'startedAt' ? 'started_at' : 'finished_at';
1201
- const orderDirection = options.orderDirection ?? 'desc';
1202
-
1203
- query = query.orderBy(orderColumn, orderDirection).limit(limit).offset(offset);
1204
-
1205
- const rows = await query.execute();
1206
-
1207
- return {
1208
- items: rows.map(row => this.mapRunRow(row)),
1209
- total: rows.length, // Simplified for transaction context
1210
- };
824
+ protected override get qb(): CoreSchemaQueryBuilder {
825
+ return this._qb;
1211
826
  }
1212
827
 
1213
- async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {
1214
- const id = generateId();
1215
-
1216
- await this.qb
1217
- .insertInto('workflow_run_steps')
1218
- .values({
1219
- id,
1220
- run_id: step.runId,
1221
- step_key: step.stepKey,
1222
- step_name: step.stepName,
1223
- status: step.status,
1224
- attempt: step.attempt,
1225
- result_json: step.result !== undefined ? JSON.stringify(step.result) : null,
1226
- error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
1227
- started_at: step.startedAt ?? null,
1228
- finished_at: step.finishedAt ?? null,
1229
- })
1230
- .execute();
1231
-
1232
- return { ...step, id };
1233
- }
1234
-
1235
- async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {
1236
- const row = await this.qb
1237
- .selectFrom('workflow_run_steps')
1238
- .selectAll()
1239
- .where('id', '=', stepId)
1240
- .executeTakeFirst();
1241
-
1242
- return row ? this.mapStepRow(row) : null;
1243
- }
1244
-
1245
- async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {
1246
- const updateData: Partial<WorkflowRunStepsTable> = {};
1247
-
1248
- if (updates.status !== undefined) updateData.status = updates.status;
1249
- if (updates.attempt !== undefined) updateData.attempt = updates.attempt;
1250
- if (updates.result !== undefined) updateData.result_json = JSON.stringify(updates.result);
1251
- if (updates.error !== undefined) updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error as WorkflowError));
1252
- if (updates.finishedAt !== undefined) updateData.finished_at = updates.finishedAt;
1253
-
1254
- if (Object.keys(updateData).length > 0) {
1255
- await this.qb.updateTable('workflow_run_steps').set(updateData).where('id', '=', stepId).execute();
1256
- }
1257
- }
1258
-
1259
- async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {
1260
- const rows = await this.qb
1261
- .selectFrom('workflow_run_steps')
1262
- .selectAll()
1263
- .where('run_id', '=', runId)
1264
- .orderBy('started_at', 'asc')
1265
- .execute();
1266
-
1267
- return rows.map(row => this.mapStepRow(row));
1268
- }
1269
-
1270
- async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {
1271
- const id = generateId();
1272
-
1273
- await this.qb
1274
- .insertInto('workflow_events')
1275
- .values({
1276
- id,
1277
- run_id: event.runId,
1278
- step_key: event.stepKey ?? null,
1279
- event_type: event.eventType,
1280
- level: event.level,
1281
- payload_json: event.payload !== undefined ? JSON.stringify(event.payload) : null,
1282
- timestamp: event.timestamp,
1283
- })
1284
- .execute();
1285
- }
1286
-
1287
- async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {
1288
- let query = this.qb
1289
- .selectFrom('workflow_events')
1290
- .selectAll()
1291
- .where('run_id', '=', runId);
1292
-
1293
- if (options.stepKey) query = query.where('step_key', '=', options.stepKey);
1294
- if (options.level) query = query.where('level', '=', options.level);
1295
-
1296
- const rows = await query
1297
- .orderBy('timestamp', 'asc')
1298
- .limit(options.limit ?? 1000)
1299
- .offset(options.offset ?? 0)
1300
- .execute();
1301
-
1302
- return rows.map(row => this.mapEventRow(row));
1303
- }
1304
-
1305
- private mapRunRow(row: WorkflowRunsTable): WorkflowRunRecord {
1306
- return {
1307
- id: row.id,
1308
- kind: row.kind,
1309
- status: row.status as RunStatus,
1310
- parentRunId: row.parent_run_id ?? undefined,
1311
- input: typeof row.input_json === 'string' ? JSON.parse(row.input_json) : row.input_json,
1312
- context: typeof row.context_json === 'string' ? JSON.parse(row.context_json) : row.context_json,
1313
- output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
1314
- error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
1315
- metadata: typeof row.metadata_json === 'string' ? JSON.parse(row.metadata_json) : row.metadata_json,
1316
- priority: row.priority ?? 0,
1317
- timeoutMs: row.timeout_ms ?? undefined,
1318
- createdAt: new Date(row.created_at),
1319
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
1320
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
1321
- };
1322
- }
1323
-
1324
- private mapStepRow(row: WorkflowRunStepsTable): WorkflowRunStepRecord {
1325
- return {
1326
- id: row.id,
1327
- runId: row.run_id,
1328
- stepKey: row.step_key,
1329
- stepName: row.step_name,
1330
- status: row.status as StepStatus,
1331
- attempt: row.attempt,
1332
- result: row.result_json ? (typeof row.result_json === 'string' ? JSON.parse(row.result_json) : row.result_json) : undefined,
1333
- error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
1334
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
1335
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
1336
- };
1337
- }
1338
-
1339
- private mapEventRow(row: WorkflowEventsTable): WorkflowEventRecord {
1340
- return {
1341
- id: row.id,
1342
- runId: row.run_id,
1343
- stepKey: row.step_key ?? undefined,
1344
- eventType: row.event_type,
1345
- level: row.level as 'info' | 'warn' | 'error',
1346
- payload: row.payload_json ? (typeof row.payload_json === 'string' ? JSON.parse(row.payload_json) : row.payload_json) : undefined,
1347
- timestamp: new Date(row.timestamp),
1348
- };
828
+ protected override get warnLabel(): string {
829
+ return 'PostgresTransactionAdapter';
1349
830
  }
1350
831
  }
1351
832