@multiplier-labs/stepflow 0.3.1 → 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.
- package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +146 -0
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +17 -5
- package/.github/workflows/security.yml +29 -0
- package/CHANGELOG.md +34 -0
- package/README.md +24 -0
- package/SECURITY.md +40 -0
- package/dist/{chunk-FQCT3I2A.js → chunk-5XJBAPXF.js} +527 -551
- package/dist/chunk-5XJBAPXF.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-FMPJBK2N.js} +67 -55
- package/dist/chunk-FMPJBK2N.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-JPXPB7U5.js} +52 -2
- package/dist/chunk-JPXPB7U5.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-U3PCVK6O.js} +276 -61
- package/dist/chunk-U3PCVK6O.js.map +1 -0
- package/dist/events/index.d.ts +60 -7
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Cd0IZ40L.d.ts} +56 -17
- package/dist/index.d.ts +15 -23
- package/dist/index.js +784 -547
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +4 -4
- package/dist/scheduler/index.js +2 -2
- package/dist/storage/index.d.ts +322 -56
- package/dist/storage/index.js +4 -2
- package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
- package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
- package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
- package/docs/stepflow-api-reference.md +5 -4
- package/package.json +12 -8
- package/src/core/engine/concurrency.ts +229 -0
- package/src/core/engine/lifecycle.ts +176 -0
- package/src/core/engine.test.ts +536 -522
- package/src/core/engine.ts +116 -235
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator/persistence.ts +165 -0
- package/src/core/orchestrator/plan-resolution.ts +140 -0
- package/src/core/orchestrator/state-transitions.ts +109 -0
- package/src/core/orchestrator/step-runner.ts +427 -0
- package/src/core/orchestrator.ts +57 -395
- package/src/core/types.ts +7 -29
- package/src/events/index.ts +4 -4
- package/src/events/memory.test.ts +39 -41
- package/src/events/memory.ts +7 -10
- package/src/events/socketio.test.ts +112 -162
- package/src/events/socketio.ts +20 -27
- package/src/events/types.ts +16 -19
- package/src/events/webhook.test.ts +489 -235
- package/src/events/webhook.ts +355 -139
- package/src/index.ts +19 -19
- package/src/planning/evaluate-condition.test.ts +153 -0
- package/src/planning/evaluate-condition.ts +105 -0
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +45 -152
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +20 -106
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +149 -150
- package/src/scheduler/cron.ts +58 -69
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +89 -105
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +40 -22
- package/src/scheduler/types.ts +4 -9
- package/src/storage/index.ts +5 -5
- package/src/storage/memory.test.ts +133 -183
- package/src/storage/memory.ts +21 -42
- package/src/storage/postgres-core.ts +550 -0
- package/src/storage/postgres-migration.test.ts +69 -0
- package/src/storage/postgres-transaction-pagination.test.ts +107 -0
- package/src/storage/postgres.ts +222 -937
- package/src/storage/sqlite-core.ts +151 -0
- package/src/storage/sqlite.test.ts +330 -281
- package/src/storage/sqlite.ts +225 -170
- package/src/storage/types.ts +33 -58
- package/src/utils/errors.test.ts +78 -80
- package/src/utils/errors.ts +27 -57
- package/src/utils/id.ts +3 -3
- package/src/utils/index.ts +6 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/pinned-fetch.test.ts +84 -0
- package/src/utils/pinned-fetch.ts +118 -0
- package/src/utils/postgres-deps.ts +20 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- package/src/utils/safe-json.test.ts +166 -0
- package/src/utils/safe-json.ts +107 -0
- package/src/utils/webhook-signature.test.ts +111 -0
- package/src/utils/webhook-signature.ts +74 -0
- package/dist/chunk-FQCT3I2A.js.map +0 -1
- package/dist/chunk-GXY66V7Q.js.map +0 -1
- package/dist/chunk-U5QGN3RT.js.map +0 -1
- package/dist/chunk-XCF4PLN7.js.map +0 -1
- package/src/scheduler/postgres-persistence.test.ts +0 -906
|
@@ -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-
|
|
8
|
+
} from "./chunk-JPXPB7U5.js";
|
|
6
9
|
|
|
7
10
|
// src/storage/memory.ts
|
|
8
11
|
var MemoryStorageAdapter = class {
|
|
@@ -84,9 +87,7 @@ var MemoryStorageAdapter = class {
|
|
|
84
87
|
}
|
|
85
88
|
/** Retrieve all step records for a workflow run, ordered by start time ascending. */
|
|
86
89
|
async getStepsForRun(runId) {
|
|
87
|
-
return Array.from(this.steps.values()).filter((s) => s.runId === runId).sort(
|
|
88
|
-
(a, b) => (a.startedAt?.getTime() ?? 0) - (b.startedAt?.getTime() ?? 0)
|
|
89
|
-
);
|
|
90
|
+
return Array.from(this.steps.values()).filter((s) => s.runId === runId).sort((a, b) => (a.startedAt?.getTime() ?? 0) - (b.startedAt?.getTime() ?? 0));
|
|
90
91
|
}
|
|
91
92
|
// ============================================================================
|
|
92
93
|
// Event Operations
|
|
@@ -101,9 +102,7 @@ var MemoryStorageAdapter = class {
|
|
|
101
102
|
}
|
|
102
103
|
/** Retrieve events for a workflow run with optional filtering and pagination. */
|
|
103
104
|
async getEventsForRun(runId, options = {}) {
|
|
104
|
-
let items = Array.from(this.events.values()).filter(
|
|
105
|
-
(e) => e.runId === runId
|
|
106
|
-
);
|
|
105
|
+
let items = Array.from(this.events.values()).filter((e) => e.runId === runId);
|
|
107
106
|
if (options.stepKey) {
|
|
108
107
|
items = items.filter((e) => e.stepKey === options.stepKey);
|
|
109
108
|
}
|
|
@@ -162,6 +161,52 @@ var MemoryStorageAdapter = class {
|
|
|
162
161
|
}
|
|
163
162
|
};
|
|
164
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
|
+
|
|
165
210
|
// src/storage/sqlite.ts
|
|
166
211
|
var CREATE_TABLES_SQL = `
|
|
167
212
|
-- Workflow runs table
|
|
@@ -173,6 +218,7 @@ CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
|
173
218
|
input_json TEXT NOT NULL,
|
|
174
219
|
metadata_json TEXT NOT NULL,
|
|
175
220
|
context_json TEXT NOT NULL,
|
|
221
|
+
completed_steps_json TEXT,
|
|
176
222
|
error_json TEXT,
|
|
177
223
|
created_at TEXT NOT NULL,
|
|
178
224
|
started_at TEXT,
|
|
@@ -217,18 +263,45 @@ CREATE TABLE IF NOT EXISTS workflow_events (
|
|
|
217
263
|
CREATE INDEX IF NOT EXISTS idx_workflow_events_run ON workflow_events(run_id);
|
|
218
264
|
CREATE INDEX IF NOT EXISTS idx_workflow_events_run_ts ON workflow_events(run_id, timestamp);
|
|
219
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
|
+
}
|
|
220
290
|
var SQLiteStorageAdapter = class {
|
|
221
291
|
db;
|
|
222
292
|
prefix;
|
|
293
|
+
logger;
|
|
223
294
|
// Prepared statements (cached for performance)
|
|
224
295
|
stmts = null;
|
|
225
296
|
constructor(config) {
|
|
226
297
|
this.db = config.db;
|
|
227
298
|
this.prefix = config.tablePrefix ?? "workflow";
|
|
299
|
+
this.logger = config.logger;
|
|
228
300
|
this.db.pragma("foreign_keys = ON");
|
|
229
301
|
if (config.autoCreateTables !== false) {
|
|
230
302
|
this.createTables();
|
|
231
303
|
}
|
|
304
|
+
this.runMigrations();
|
|
232
305
|
this.prepareStatements();
|
|
233
306
|
}
|
|
234
307
|
/**
|
|
@@ -237,14 +310,44 @@ var SQLiteStorageAdapter = class {
|
|
|
237
310
|
createTables() {
|
|
238
311
|
this.db.exec(CREATE_TABLES_SQL);
|
|
239
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
|
+
}
|
|
240
343
|
/**
|
|
241
344
|
* Prepare all SQL statements for better performance.
|
|
242
345
|
*/
|
|
243
346
|
prepareStatements() {
|
|
244
347
|
this.stmts = {
|
|
245
348
|
insertRun: this.db.prepare(`
|
|
246
|
-
INSERT INTO workflow_runs (id, kind, status, parent_run_id, input_json, metadata_json, context_json, error_json, created_at, started_at, finished_at)
|
|
247
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
349
|
+
INSERT INTO workflow_runs (id, kind, status, parent_run_id, input_json, metadata_json, context_json, completed_steps_json, error_json, created_at, started_at, finished_at)
|
|
350
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
248
351
|
`),
|
|
249
352
|
getRun: this.db.prepare(`
|
|
250
353
|
SELECT * FROM workflow_runs WHERE id = ?
|
|
@@ -253,6 +356,7 @@ var SQLiteStorageAdapter = class {
|
|
|
253
356
|
UPDATE workflow_runs
|
|
254
357
|
SET status = COALESCE(?, status),
|
|
255
358
|
context_json = COALESCE(?, context_json),
|
|
359
|
+
completed_steps_json = COALESCE(?, completed_steps_json),
|
|
256
360
|
error_json = COALESCE(?, error_json),
|
|
257
361
|
started_at = COALESCE(?, started_at),
|
|
258
362
|
finished_at = COALESCE(?, finished_at)
|
|
@@ -344,6 +448,7 @@ var SQLiteStorageAdapter = class {
|
|
|
344
448
|
JSON.stringify(run.input),
|
|
345
449
|
JSON.stringify(run.metadata),
|
|
346
450
|
JSON.stringify(run.context),
|
|
451
|
+
run.completedSteps ? JSON.stringify(run.completedSteps) : null,
|
|
347
452
|
run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
|
|
348
453
|
createdAt.toISOString(),
|
|
349
454
|
run.startedAt?.toISOString() ?? null,
|
|
@@ -357,12 +462,13 @@ var SQLiteStorageAdapter = class {
|
|
|
357
462
|
}
|
|
358
463
|
async getRun(runId) {
|
|
359
464
|
const row = this.stmts.getRun.get(runId);
|
|
360
|
-
return row ?
|
|
465
|
+
return row ? mapRunRow(row, this.mapperCtx) : null;
|
|
361
466
|
}
|
|
362
467
|
async updateRun(runId, updates) {
|
|
363
468
|
this.stmts.updateRun.run(
|
|
364
469
|
updates.status ?? null,
|
|
365
470
|
updates.context ? JSON.stringify(updates.context) : null,
|
|
471
|
+
updates.completedSteps ? JSON.stringify(updates.completedSteps) : null,
|
|
366
472
|
updates.error ? JSON.stringify(sanitizeErrorForStorage(updates.error)) : null,
|
|
367
473
|
updates.startedAt?.toISOString() ?? null,
|
|
368
474
|
updates.finishedAt?.toISOString() ?? null,
|
|
@@ -395,7 +501,7 @@ var SQLiteStorageAdapter = class {
|
|
|
395
501
|
parentRunId
|
|
396
502
|
);
|
|
397
503
|
return {
|
|
398
|
-
items: rows.map((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 ?
|
|
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) =>
|
|
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) =>
|
|
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
|
|
479
|
-
*
|
|
480
|
-
* If the callback awaits real async I/O (network,
|
|
481
|
-
*
|
|
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) =>
|
|
649
|
+
return rows.map((row) => mapRunRow(row, this.mapperCtx));
|
|
532
650
|
}
|
|
533
651
|
/**
|
|
534
652
|
* Get the last completed step for a run.
|
|
@@ -536,50 +654,20 @@ var SQLiteStorageAdapter = class {
|
|
|
536
654
|
*/
|
|
537
655
|
async getLastCompletedStep(runId) {
|
|
538
656
|
const row = this.stmts.getLastCompletedStep.get(runId);
|
|
539
|
-
return row ?
|
|
657
|
+
return row ? mapStepRow(row, this.mapperCtx) : null;
|
|
540
658
|
}
|
|
541
659
|
// ============================================================================
|
|
542
660
|
// Row Mapping
|
|
543
661
|
// ============================================================================
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
error: row.error_json ? JSON.parse(row.error_json) : void 0,
|
|
554
|
-
createdAt: new Date(row.created_at),
|
|
555
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
556
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
mapStepRow(row) {
|
|
560
|
-
return {
|
|
561
|
-
id: row.id,
|
|
562
|
-
runId: row.run_id,
|
|
563
|
-
stepKey: row.step_key,
|
|
564
|
-
stepName: row.step_name,
|
|
565
|
-
status: row.status,
|
|
566
|
-
attempt: row.attempt,
|
|
567
|
-
result: row.result_json ? JSON.parse(row.result_json) : void 0,
|
|
568
|
-
error: row.error_json ? JSON.parse(row.error_json) : void 0,
|
|
569
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
570
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
mapEventRow(row) {
|
|
574
|
-
return {
|
|
575
|
-
id: row.id,
|
|
576
|
-
runId: row.run_id,
|
|
577
|
-
stepKey: row.step_key ?? void 0,
|
|
578
|
-
eventType: row.event_type,
|
|
579
|
-
level: row.level,
|
|
580
|
-
payload: row.payload_json ? JSON.parse(row.payload_json) : void 0,
|
|
581
|
-
timestamp: new Date(row.timestamp)
|
|
582
|
-
};
|
|
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 };
|
|
583
671
|
}
|
|
584
672
|
// ============================================================================
|
|
585
673
|
// Utility Methods
|
|
@@ -605,6 +693,266 @@ var SQLiteStorageAdapter = class {
|
|
|
605
693
|
}
|
|
606
694
|
};
|
|
607
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
|
+
/** Mapper context handed to row mappers — pairs the component label with the optional logger. */
|
|
768
|
+
get mapperCtx() {
|
|
769
|
+
return { component: this.warnLabel, logger: this.logger };
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Hook called at the start of every CRUD method. The pool-owning adapter
|
|
773
|
+
* overrides this to throw before `initialize()` has run; the transaction
|
|
774
|
+
* wrapper leaves it as a no-op.
|
|
775
|
+
*/
|
|
776
|
+
ensureReady() {
|
|
777
|
+
}
|
|
778
|
+
// --------------------------------------------------------------------------
|
|
779
|
+
// Run operations
|
|
780
|
+
// --------------------------------------------------------------------------
|
|
781
|
+
async createRun(run) {
|
|
782
|
+
this.ensureReady();
|
|
783
|
+
const id = "id" in run && run.id ? run.id : generateId();
|
|
784
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
785
|
+
await this.qb.insertInto("runs").values({
|
|
786
|
+
id,
|
|
787
|
+
kind: run.kind,
|
|
788
|
+
status: run.status,
|
|
789
|
+
parent_run_id: "parentRunId" in run ? run.parentRunId ?? null : null,
|
|
790
|
+
input_json: JSON.stringify(run.input),
|
|
791
|
+
metadata_json: JSON.stringify(run.metadata ?? {}),
|
|
792
|
+
context_json: JSON.stringify(run.context ?? {}),
|
|
793
|
+
output_json: null,
|
|
794
|
+
error_json: "error" in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
|
|
795
|
+
priority: "priority" in run ? run.priority ?? 0 : 0,
|
|
796
|
+
timeout_ms: "timeoutMs" in run ? run.timeoutMs ?? null : null,
|
|
797
|
+
created_at: createdAt,
|
|
798
|
+
started_at: null,
|
|
799
|
+
finished_at: null
|
|
800
|
+
}).execute();
|
|
801
|
+
return {
|
|
802
|
+
id,
|
|
803
|
+
kind: run.kind,
|
|
804
|
+
status: run.status,
|
|
805
|
+
parentRunId: "parentRunId" in run ? run.parentRunId : void 0,
|
|
806
|
+
input: run.input,
|
|
807
|
+
context: run.context ?? {},
|
|
808
|
+
output: void 0,
|
|
809
|
+
error: void 0,
|
|
810
|
+
metadata: run.metadata ?? {},
|
|
811
|
+
priority: "priority" in run ? run.priority ?? 0 : 0,
|
|
812
|
+
timeoutMs: "timeoutMs" in run ? run.timeoutMs : void 0,
|
|
813
|
+
createdAt
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
async getRun(runId) {
|
|
817
|
+
this.ensureReady();
|
|
818
|
+
const row = await this.qb.selectFrom("runs").selectAll().where("id", "=", runId).executeTakeFirst();
|
|
819
|
+
return row ? mapRunRow2(row, this.mapperCtx) : null;
|
|
820
|
+
}
|
|
821
|
+
async updateRun(runId, updates) {
|
|
822
|
+
this.ensureReady();
|
|
823
|
+
const updateData = {};
|
|
824
|
+
if (updates.status !== void 0) {
|
|
825
|
+
updateData.status = updates.status;
|
|
826
|
+
}
|
|
827
|
+
if (updates.context !== void 0) {
|
|
828
|
+
updateData.context_json = JSON.stringify(updates.context);
|
|
829
|
+
}
|
|
830
|
+
if ("output" in updates && updates.output !== void 0) {
|
|
831
|
+
updateData.output_json = JSON.stringify(updates.output);
|
|
832
|
+
}
|
|
833
|
+
if (updates.error !== void 0) {
|
|
834
|
+
updateData.error_json = JSON.stringify(
|
|
835
|
+
sanitizeErrorForStorage(updates.error)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
if (updates.startedAt !== void 0) {
|
|
839
|
+
updateData.started_at = updates.startedAt;
|
|
840
|
+
}
|
|
841
|
+
if (updates.finishedAt !== void 0) {
|
|
842
|
+
updateData.finished_at = updates.finishedAt;
|
|
843
|
+
}
|
|
844
|
+
if (Object.keys(updateData).length > 0) {
|
|
845
|
+
await this.qb.updateTable("runs").set(updateData).where("id", "=", runId).execute();
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async listRuns(options = {}) {
|
|
849
|
+
this.ensureReady();
|
|
850
|
+
const { sql: sql2 } = requirePostgresDeps();
|
|
851
|
+
const limit = options.limit ?? 50;
|
|
852
|
+
const offset = options.offset ?? 0;
|
|
853
|
+
const countQuery = applyRunsFilters(
|
|
854
|
+
this.qb.selectFrom("runs").select(sql2`count(*)`.as("count")),
|
|
855
|
+
options
|
|
856
|
+
);
|
|
857
|
+
const dataQuery = applyRunsFilters(this.qb.selectFrom("runs").selectAll(), options);
|
|
858
|
+
const orderBy = options.orderBy ?? "createdAt";
|
|
859
|
+
const orderDirection = options.orderDirection ?? "desc";
|
|
860
|
+
const orderColumn = orderBy === "createdAt" ? "created_at" : orderBy === "startedAt" ? "started_at" : "finished_at";
|
|
861
|
+
const countResult = await countQuery.executeTakeFirst();
|
|
862
|
+
const total = Number(countResult?.count ?? 0);
|
|
863
|
+
const rows = await dataQuery.orderBy(orderColumn, orderDirection).limit(limit).offset(offset).execute();
|
|
864
|
+
return {
|
|
865
|
+
items: rows.map((row) => mapRunRow2(row, this.mapperCtx)),
|
|
866
|
+
total
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
// --------------------------------------------------------------------------
|
|
870
|
+
// Step operations
|
|
871
|
+
// --------------------------------------------------------------------------
|
|
872
|
+
async createStep(step) {
|
|
873
|
+
this.ensureReady();
|
|
874
|
+
const id = generateId();
|
|
875
|
+
await this.qb.insertInto("workflow_run_steps").values({
|
|
876
|
+
id,
|
|
877
|
+
run_id: step.runId,
|
|
878
|
+
step_key: step.stepKey,
|
|
879
|
+
step_name: step.stepName,
|
|
880
|
+
status: step.status,
|
|
881
|
+
attempt: step.attempt,
|
|
882
|
+
result_json: step.result !== void 0 ? JSON.stringify(step.result) : null,
|
|
883
|
+
error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
|
|
884
|
+
started_at: step.startedAt ?? null,
|
|
885
|
+
finished_at: step.finishedAt ?? null
|
|
886
|
+
}).execute();
|
|
887
|
+
return { ...step, id };
|
|
888
|
+
}
|
|
889
|
+
async getStep(stepId) {
|
|
890
|
+
this.ensureReady();
|
|
891
|
+
const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("id", "=", stepId).executeTakeFirst();
|
|
892
|
+
return row ? mapStepRow2(row, this.mapperCtx) : null;
|
|
893
|
+
}
|
|
894
|
+
async updateStep(stepId, updates) {
|
|
895
|
+
this.ensureReady();
|
|
896
|
+
const updateData = {};
|
|
897
|
+
if (updates.status !== void 0) {
|
|
898
|
+
updateData.status = updates.status;
|
|
899
|
+
}
|
|
900
|
+
if (updates.attempt !== void 0) {
|
|
901
|
+
updateData.attempt = updates.attempt;
|
|
902
|
+
}
|
|
903
|
+
if (updates.result !== void 0) {
|
|
904
|
+
updateData.result_json = JSON.stringify(updates.result);
|
|
905
|
+
}
|
|
906
|
+
if (updates.error !== void 0) {
|
|
907
|
+
updateData.error_json = JSON.stringify(
|
|
908
|
+
sanitizeErrorForStorage(updates.error)
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (updates.finishedAt !== void 0) {
|
|
912
|
+
updateData.finished_at = updates.finishedAt;
|
|
913
|
+
}
|
|
914
|
+
if (Object.keys(updateData).length > 0) {
|
|
915
|
+
await this.qb.updateTable("workflow_run_steps").set(updateData).where("id", "=", stepId).execute();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async getStepsForRun(runId) {
|
|
919
|
+
this.ensureReady();
|
|
920
|
+
const rows = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
|
|
921
|
+
return rows.map((row) => mapStepRow2(row, this.mapperCtx));
|
|
922
|
+
}
|
|
923
|
+
// --------------------------------------------------------------------------
|
|
924
|
+
// Event operations
|
|
925
|
+
// --------------------------------------------------------------------------
|
|
926
|
+
async saveEvent(event) {
|
|
927
|
+
this.ensureReady();
|
|
928
|
+
const id = generateId();
|
|
929
|
+
await this.qb.insertInto("workflow_events").values({
|
|
930
|
+
id,
|
|
931
|
+
run_id: event.runId,
|
|
932
|
+
step_key: event.stepKey ?? null,
|
|
933
|
+
event_type: event.eventType,
|
|
934
|
+
level: event.level,
|
|
935
|
+
payload_json: event.payload !== void 0 ? JSON.stringify(event.payload) : null,
|
|
936
|
+
timestamp: event.timestamp
|
|
937
|
+
}).execute();
|
|
938
|
+
}
|
|
939
|
+
async getEventsForRun(runId, options = {}) {
|
|
940
|
+
this.ensureReady();
|
|
941
|
+
const limit = options.limit ?? 1e3;
|
|
942
|
+
const offset = options.offset ?? 0;
|
|
943
|
+
let query = this.qb.selectFrom("workflow_events").selectAll().where("run_id", "=", runId);
|
|
944
|
+
if (options.stepKey) {
|
|
945
|
+
query = query.where("step_key", "=", options.stepKey);
|
|
946
|
+
}
|
|
947
|
+
if (options.level) {
|
|
948
|
+
query = query.where("level", "=", options.level);
|
|
949
|
+
}
|
|
950
|
+
query = query.orderBy("timestamp", "asc").limit(limit).offset(offset);
|
|
951
|
+
const rows = await query.execute();
|
|
952
|
+
return rows.map((row) => mapEventRow2(row, this.mapperCtx));
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
608
956
|
// src/storage/postgres.ts
|
|
609
957
|
var Kysely;
|
|
610
958
|
var PostgresDialect;
|
|
@@ -614,31 +962,48 @@ function stripStack(error) {
|
|
|
614
962
|
const { stack: _stack, ...rest } = error;
|
|
615
963
|
return rest;
|
|
616
964
|
}
|
|
617
|
-
var PostgresStorageAdapter = class {
|
|
965
|
+
var PostgresStorageAdapter = class _PostgresStorageAdapter extends PostgresStorageCore {
|
|
618
966
|
db;
|
|
619
967
|
pool;
|
|
620
968
|
ownsPool = false;
|
|
621
|
-
schema;
|
|
622
969
|
autoMigrate;
|
|
623
970
|
initialized = false;
|
|
624
971
|
config;
|
|
625
972
|
constructor(config) {
|
|
626
|
-
|
|
627
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(
|
|
973
|
+
const schema = config.schema ?? "public";
|
|
974
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(schema)) {
|
|
628
975
|
throw new Error(
|
|
629
|
-
`Invalid schema name "${
|
|
976
|
+
`Invalid schema name "${schema}". Schema must start with a letter or underscore, contain only alphanumeric characters and underscores, and be at most 63 characters.`
|
|
630
977
|
);
|
|
631
978
|
}
|
|
979
|
+
super(schema, config.logger);
|
|
632
980
|
this.autoMigrate = config.autoMigrate !== false;
|
|
633
981
|
this.config = config;
|
|
634
982
|
}
|
|
635
983
|
/**
|
|
636
|
-
*
|
|
637
|
-
*
|
|
984
|
+
* Core-tables query builder consumed by `PostgresStorageCore`. Returns the
|
|
985
|
+
* schema-scoped Kysely view typed against the three core tables, which is
|
|
986
|
+
* what the shared CRUD methods need.
|
|
638
987
|
*/
|
|
639
988
|
get qb() {
|
|
640
989
|
return this.db.withSchema(this.schema);
|
|
641
990
|
}
|
|
991
|
+
/**
|
|
992
|
+
* Extended query builder for adapter-only tables (currently
|
|
993
|
+
* `stepflow_step_results`). Kept separate from `qb` so the base class stays
|
|
994
|
+
* typed against the core schema.
|
|
995
|
+
*/
|
|
996
|
+
get extQb() {
|
|
997
|
+
return this.db.withSchema(this.schema);
|
|
998
|
+
}
|
|
999
|
+
/** Tag used by shared row mappers in `console.warn` messages. */
|
|
1000
|
+
get warnLabel() {
|
|
1001
|
+
return "PostgresStorageAdapter";
|
|
1002
|
+
}
|
|
1003
|
+
/** Override of `PostgresStorageCore.ensureReady()` — enforces `initialize()`. */
|
|
1004
|
+
ensureReady() {
|
|
1005
|
+
this.ensureInitialized();
|
|
1006
|
+
}
|
|
642
1007
|
ensureInitialized() {
|
|
643
1008
|
if (!this.initialized) {
|
|
644
1009
|
throw new Error(
|
|
@@ -663,9 +1028,7 @@ var PostgresStorageAdapter = class {
|
|
|
663
1028
|
this.pool = this.config.pool;
|
|
664
1029
|
this.ownsPool = false;
|
|
665
1030
|
} else if (this.config.connectionString) {
|
|
666
|
-
this.pool = new pgModule.Pool({
|
|
667
|
-
connectionString: this.config.connectionString
|
|
668
|
-
});
|
|
1031
|
+
this.pool = new pgModule.Pool({ connectionString: this.config.connectionString });
|
|
669
1032
|
this.ownsPool = true;
|
|
670
1033
|
} else if (this.config.poolConfig) {
|
|
671
1034
|
this.pool = new pgModule.Pool(this.config.poolConfig);
|
|
@@ -695,14 +1058,50 @@ var PostgresStorageAdapter = class {
|
|
|
695
1058
|
// ============================================================================
|
|
696
1059
|
// Schema Creation
|
|
697
1060
|
// ============================================================================
|
|
1061
|
+
/**
|
|
1062
|
+
* Run a single migration step. Tolerates only known-benign Postgres errors
|
|
1063
|
+
* (duplicate object codes that map to "already applied"), and rethrows
|
|
1064
|
+
* everything else with a clear message so partial-state upgrades are not
|
|
1065
|
+
* silently swallowed.
|
|
1066
|
+
*/
|
|
1067
|
+
async runMigration(label, fn) {
|
|
1068
|
+
try {
|
|
1069
|
+
await fn();
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
if (_PostgresStorageAdapter.isBenignMigrationError(err)) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1075
|
+
throw new Error(`Schema migration failed (${label}): ${message}`, { cause: err });
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Postgres SQLSTATE codes that indicate a migration step has already been
|
|
1080
|
+
* applied (object already exists). These are safe to ignore on idempotent
|
|
1081
|
+
* re-runs; everything else must propagate.
|
|
1082
|
+
*
|
|
1083
|
+
* - 42701: duplicate_column
|
|
1084
|
+
* - 42P07: duplicate_table
|
|
1085
|
+
* - 42P06: duplicate_schema
|
|
1086
|
+
* - 42710: duplicate_object (e.g., index, constraint)
|
|
1087
|
+
*/
|
|
1088
|
+
static BENIGN_MIGRATION_CODES = /* @__PURE__ */ new Set([
|
|
1089
|
+
"42701",
|
|
1090
|
+
"42P07",
|
|
1091
|
+
"42P06",
|
|
1092
|
+
"42710"
|
|
1093
|
+
]);
|
|
1094
|
+
static isBenignMigrationError(err) {
|
|
1095
|
+
if (!err || typeof err !== "object") return false;
|
|
1096
|
+
const code = err.code;
|
|
1097
|
+
return typeof code === "string" && _PostgresStorageAdapter.BENIGN_MIGRATION_CODES.has(code);
|
|
1098
|
+
}
|
|
698
1099
|
/**
|
|
699
1100
|
* Create the workflow tables if they don't exist.
|
|
700
1101
|
*/
|
|
701
1102
|
async createTables() {
|
|
702
1103
|
if (this.schema !== "public") {
|
|
703
|
-
await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(
|
|
704
|
-
this.db
|
|
705
|
-
);
|
|
1104
|
+
await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(this.db);
|
|
706
1105
|
}
|
|
707
1106
|
await sql`
|
|
708
1107
|
CREATE TABLE IF NOT EXISTS ${sql.table(`${this.schema}.runs`)} (
|
|
@@ -725,13 +1124,15 @@ var PostgresStorageAdapter = class {
|
|
|
725
1124
|
)
|
|
726
1125
|
)
|
|
727
1126
|
`.execute(this.db);
|
|
728
|
-
await
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1127
|
+
await this.runMigration(
|
|
1128
|
+
`add columns to ${this.schema}.runs`,
|
|
1129
|
+
() => sql`
|
|
1130
|
+
ALTER TABLE ${sql.table(`${this.schema}.runs`)}
|
|
1131
|
+
ADD COLUMN IF NOT EXISTS output_json JSONB,
|
|
1132
|
+
ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,
|
|
1133
|
+
ADD COLUMN IF NOT EXISTS timeout_ms INTEGER
|
|
1134
|
+
`.execute(this.db)
|
|
1135
|
+
);
|
|
735
1136
|
await sql`
|
|
736
1137
|
CREATE INDEX IF NOT EXISTS idx_runs_kind_status
|
|
737
1138
|
ON ${sql.table(`${this.schema}.runs`)} (kind, status)
|
|
@@ -826,224 +1227,24 @@ var PostgresStorageAdapter = class {
|
|
|
826
1227
|
`.execute(this.db);
|
|
827
1228
|
}
|
|
828
1229
|
// ============================================================================
|
|
829
|
-
// Run
|
|
1230
|
+
// Run / Step / Event CRUD
|
|
1231
|
+
// ============================================================================
|
|
1232
|
+
//
|
|
1233
|
+
// The CRUD methods (`createRun`, `getRun`, `updateRun`, `listRuns`,
|
|
1234
|
+
// `createStep`, `getStep`, `updateStep`, `getStepsForRun`, `saveEvent`,
|
|
1235
|
+
// `getEventsForRun`) are inherited from `PostgresStorageCore`. Each call
|
|
1236
|
+
// routes through `this.qb` (overridden above) and `this.ensureReady()`
|
|
1237
|
+
// which calls `this.ensureInitialized()`.
|
|
1238
|
+
// ============================================================================
|
|
1239
|
+
// Transaction Support
|
|
830
1240
|
// ============================================================================
|
|
831
1241
|
/**
|
|
832
|
-
*
|
|
833
|
-
* Supports both legacy and new CreateRunInput interfaces.
|
|
1242
|
+
* Execute a function within a database transaction.
|
|
834
1243
|
*/
|
|
835
|
-
async
|
|
836
|
-
this.ensureInitialized();
|
|
837
|
-
const id = "id" in run && run.id ? run.id : generateId();
|
|
838
|
-
const createdAt = /* @__PURE__ */ new Date();
|
|
839
|
-
await this.qb.insertInto("runs").values({
|
|
840
|
-
id,
|
|
841
|
-
kind: run.kind,
|
|
842
|
-
status: run.status,
|
|
843
|
-
parent_run_id: "parentRunId" in run ? run.parentRunId ?? null : null,
|
|
844
|
-
input_json: JSON.stringify(run.input),
|
|
845
|
-
metadata_json: JSON.stringify(run.metadata ?? {}),
|
|
846
|
-
context_json: JSON.stringify(run.context ?? {}),
|
|
847
|
-
// Default to empty object
|
|
848
|
-
output_json: null,
|
|
849
|
-
error_json: "error" in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
|
|
850
|
-
priority: "priority" in run ? run.priority ?? 0 : 0,
|
|
851
|
-
timeout_ms: "timeoutMs" in run ? run.timeoutMs ?? null : null,
|
|
852
|
-
created_at: createdAt,
|
|
853
|
-
started_at: null,
|
|
854
|
-
finished_at: null
|
|
855
|
-
}).execute();
|
|
856
|
-
return {
|
|
857
|
-
id,
|
|
858
|
-
kind: run.kind,
|
|
859
|
-
status: run.status,
|
|
860
|
-
parentRunId: "parentRunId" in run ? run.parentRunId : void 0,
|
|
861
|
-
input: run.input,
|
|
862
|
-
context: run.context ?? {},
|
|
863
|
-
output: void 0,
|
|
864
|
-
error: void 0,
|
|
865
|
-
metadata: run.metadata ?? {},
|
|
866
|
-
priority: "priority" in run ? run.priority ?? 0 : 0,
|
|
867
|
-
timeoutMs: "timeoutMs" in run ? run.timeoutMs : void 0,
|
|
868
|
-
createdAt
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
async getRun(runId) {
|
|
872
|
-
this.ensureInitialized();
|
|
873
|
-
const row = await this.qb.selectFrom("runs").selectAll().where("id", "=", runId).executeTakeFirst();
|
|
874
|
-
return row ? this.mapRunRow(row) : null;
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Update a workflow run.
|
|
878
|
-
* Supports both legacy Partial<WorkflowRunRecord> and new UpdateRunInput interfaces.
|
|
879
|
-
*/
|
|
880
|
-
async updateRun(runId, updates) {
|
|
881
|
-
this.ensureInitialized();
|
|
882
|
-
const updateData = {};
|
|
883
|
-
if (updates.status !== void 0) {
|
|
884
|
-
updateData.status = updates.status;
|
|
885
|
-
}
|
|
886
|
-
if (updates.context !== void 0) {
|
|
887
|
-
updateData.context_json = JSON.stringify(updates.context);
|
|
888
|
-
}
|
|
889
|
-
if ("output" in updates && updates.output !== void 0) {
|
|
890
|
-
updateData.output_json = JSON.stringify(updates.output);
|
|
891
|
-
}
|
|
892
|
-
if (updates.error !== void 0) {
|
|
893
|
-
updateData.error_json = JSON.stringify(
|
|
894
|
-
sanitizeErrorForStorage(updates.error)
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
if (updates.startedAt !== void 0) {
|
|
898
|
-
updateData.started_at = updates.startedAt;
|
|
899
|
-
}
|
|
900
|
-
if (updates.finishedAt !== void 0) {
|
|
901
|
-
updateData.finished_at = updates.finishedAt;
|
|
902
|
-
}
|
|
903
|
-
if (Object.keys(updateData).length > 0) {
|
|
904
|
-
await this.qb.updateTable("runs").set(updateData).where("id", "=", runId).execute();
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
/**
|
|
908
|
-
* List workflow runs with filtering and pagination.
|
|
909
|
-
*/
|
|
910
|
-
/**
|
|
911
|
-
* Apply common run filters to a Kysely query builder.
|
|
912
|
-
* Used by both the data query and count query in listRuns to avoid duplication.
|
|
913
|
-
*/
|
|
914
|
-
applyRunsFilters(query, options) {
|
|
915
|
-
if (options.kind) {
|
|
916
|
-
query = query.where("kind", "=", options.kind);
|
|
917
|
-
}
|
|
918
|
-
if (options.status) {
|
|
919
|
-
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
920
|
-
query = query.where("status", "in", statuses);
|
|
921
|
-
}
|
|
922
|
-
if (options.parentRunId !== void 0) {
|
|
923
|
-
query = query.where("parent_run_id", "=", options.parentRunId);
|
|
924
|
-
}
|
|
925
|
-
return query;
|
|
926
|
-
}
|
|
927
|
-
async listRuns(options = {}) {
|
|
928
|
-
this.ensureInitialized();
|
|
929
|
-
const limit = options.limit ?? 50;
|
|
930
|
-
const offset = options.offset ?? 0;
|
|
931
|
-
let query = this.applyRunsFilters(
|
|
932
|
-
this.qb.selectFrom("runs").selectAll(),
|
|
933
|
-
options
|
|
934
|
-
);
|
|
935
|
-
const countQuery = this.applyRunsFilters(
|
|
936
|
-
this.qb.selectFrom("runs").select(sql`count(*)`.as("count")),
|
|
937
|
-
options
|
|
938
|
-
);
|
|
939
|
-
const countResult = await countQuery.executeTakeFirst();
|
|
940
|
-
const total = Number(countResult?.count ?? 0);
|
|
941
|
-
const orderBy = options.orderBy ?? "createdAt";
|
|
942
|
-
const orderDirection = options.orderDirection ?? "desc";
|
|
943
|
-
const orderColumn = orderBy === "createdAt" ? "created_at" : orderBy === "startedAt" ? "started_at" : "finished_at";
|
|
944
|
-
query = query.orderBy(orderColumn, orderDirection);
|
|
945
|
-
query = query.limit(limit).offset(offset);
|
|
946
|
-
const rows = await query.execute();
|
|
947
|
-
return {
|
|
948
|
-
items: rows.map((row) => this.mapRunRow(row)),
|
|
949
|
-
total
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
// ============================================================================
|
|
953
|
-
// Step Operations
|
|
954
|
-
// ============================================================================
|
|
955
|
-
async createStep(step) {
|
|
956
|
-
this.ensureInitialized();
|
|
957
|
-
const id = generateId();
|
|
958
|
-
await this.qb.insertInto("workflow_run_steps").values({
|
|
959
|
-
id,
|
|
960
|
-
run_id: step.runId,
|
|
961
|
-
step_key: step.stepKey,
|
|
962
|
-
step_name: step.stepName,
|
|
963
|
-
status: step.status,
|
|
964
|
-
attempt: step.attempt,
|
|
965
|
-
result_json: step.result !== void 0 ? JSON.stringify(step.result) : null,
|
|
966
|
-
error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
|
|
967
|
-
started_at: step.startedAt ?? null,
|
|
968
|
-
finished_at: step.finishedAt ?? null
|
|
969
|
-
}).execute();
|
|
970
|
-
return { ...step, id };
|
|
971
|
-
}
|
|
972
|
-
async getStep(stepId) {
|
|
973
|
-
this.ensureInitialized();
|
|
974
|
-
const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("id", "=", stepId).executeTakeFirst();
|
|
975
|
-
return row ? this.mapStepRow(row) : null;
|
|
976
|
-
}
|
|
977
|
-
async updateStep(stepId, updates) {
|
|
978
|
-
this.ensureInitialized();
|
|
979
|
-
const updateData = {};
|
|
980
|
-
if (updates.status !== void 0) {
|
|
981
|
-
updateData.status = updates.status;
|
|
982
|
-
}
|
|
983
|
-
if (updates.attempt !== void 0) {
|
|
984
|
-
updateData.attempt = updates.attempt;
|
|
985
|
-
}
|
|
986
|
-
if (updates.result !== void 0) {
|
|
987
|
-
updateData.result_json = JSON.stringify(updates.result);
|
|
988
|
-
}
|
|
989
|
-
if (updates.error !== void 0) {
|
|
990
|
-
updateData.error_json = JSON.stringify(
|
|
991
|
-
sanitizeErrorForStorage(updates.error)
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
if (updates.finishedAt !== void 0) {
|
|
995
|
-
updateData.finished_at = updates.finishedAt;
|
|
996
|
-
}
|
|
997
|
-
if (Object.keys(updateData).length > 0) {
|
|
998
|
-
await this.qb.updateTable("workflow_run_steps").set(updateData).where("id", "=", stepId).execute();
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
async getStepsForRun(runId) {
|
|
1002
|
-
this.ensureInitialized();
|
|
1003
|
-
const rows = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
|
|
1004
|
-
return rows.map((row) => this.mapStepRow(row));
|
|
1005
|
-
}
|
|
1006
|
-
// ============================================================================
|
|
1007
|
-
// Event Operations
|
|
1008
|
-
// ============================================================================
|
|
1009
|
-
async saveEvent(event) {
|
|
1010
|
-
this.ensureInitialized();
|
|
1011
|
-
const id = generateId();
|
|
1012
|
-
await this.qb.insertInto("workflow_events").values({
|
|
1013
|
-
id,
|
|
1014
|
-
run_id: event.runId,
|
|
1015
|
-
step_key: event.stepKey ?? null,
|
|
1016
|
-
event_type: event.eventType,
|
|
1017
|
-
level: event.level,
|
|
1018
|
-
payload_json: event.payload !== void 0 ? JSON.stringify(event.payload) : null,
|
|
1019
|
-
timestamp: event.timestamp
|
|
1020
|
-
}).execute();
|
|
1021
|
-
}
|
|
1022
|
-
async getEventsForRun(runId, options = {}) {
|
|
1023
|
-
this.ensureInitialized();
|
|
1024
|
-
const limit = options.limit ?? 1e3;
|
|
1025
|
-
const offset = options.offset ?? 0;
|
|
1026
|
-
let query = this.qb.selectFrom("workflow_events").selectAll().where("run_id", "=", runId);
|
|
1027
|
-
if (options.stepKey) {
|
|
1028
|
-
query = query.where("step_key", "=", options.stepKey);
|
|
1029
|
-
}
|
|
1030
|
-
if (options.level) {
|
|
1031
|
-
query = query.where("level", "=", options.level);
|
|
1032
|
-
}
|
|
1033
|
-
query = query.orderBy("timestamp", "asc").limit(limit).offset(offset);
|
|
1034
|
-
const rows = await query.execute();
|
|
1035
|
-
return rows.map((row) => this.mapEventRow(row));
|
|
1036
|
-
}
|
|
1037
|
-
// ============================================================================
|
|
1038
|
-
// Transaction Support
|
|
1039
|
-
// ============================================================================
|
|
1040
|
-
/**
|
|
1041
|
-
* Execute a function within a database transaction.
|
|
1042
|
-
*/
|
|
1043
|
-
async transaction(fn) {
|
|
1244
|
+
async transaction(fn) {
|
|
1044
1245
|
this.ensureInitialized();
|
|
1045
1246
|
return await this.db.transaction().execute(async (trx) => {
|
|
1046
|
-
const txAdapter = new PostgresTransactionAdapter(trx, this.schema);
|
|
1247
|
+
const txAdapter = new PostgresTransactionAdapter(trx, this.schema, this.logger);
|
|
1047
1248
|
return await fn(txAdapter);
|
|
1048
1249
|
});
|
|
1049
1250
|
}
|
|
@@ -1069,7 +1270,7 @@ var PostgresStorageAdapter = class {
|
|
|
1069
1270
|
async getInterruptedRuns() {
|
|
1070
1271
|
this.ensureInitialized();
|
|
1071
1272
|
const rows = await this.qb.selectFrom("runs").selectAll().where("status", "in", ["queued", "running"]).orderBy("created_at", "asc").execute();
|
|
1072
|
-
return rows.map((row) => this.
|
|
1273
|
+
return rows.map((row) => mapRunRow2(row, this.mapperCtx));
|
|
1073
1274
|
}
|
|
1074
1275
|
/**
|
|
1075
1276
|
* Get the last completed step for a run.
|
|
@@ -1078,7 +1279,7 @@ var PostgresStorageAdapter = class {
|
|
|
1078
1279
|
async getLastCompletedStep(runId) {
|
|
1079
1280
|
this.ensureInitialized();
|
|
1080
1281
|
const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).where("status", "=", "succeeded").orderBy("finished_at", "desc").limit(1).executeTakeFirst();
|
|
1081
|
-
return row ? this.
|
|
1282
|
+
return row ? mapStepRow2(row, this.mapperCtx) : null;
|
|
1082
1283
|
}
|
|
1083
1284
|
// ============================================================================
|
|
1084
1285
|
// Atomic Dequeue (for distributed workers)
|
|
@@ -1106,87 +1307,37 @@ var PostgresStorageAdapter = class {
|
|
|
1106
1307
|
RETURNING *
|
|
1107
1308
|
`.execute(this.db);
|
|
1108
1309
|
const row = result.rows[0];
|
|
1109
|
-
return row ? this.
|
|
1310
|
+
return row ? mapRunRow2(row, this.mapperCtx) : null;
|
|
1110
1311
|
}
|
|
1111
1312
|
// ============================================================================
|
|
1112
|
-
// Row Mapping
|
|
1313
|
+
// Row Mapping (adapter-only tables)
|
|
1113
1314
|
// ============================================================================
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
status: row.status,
|
|
1119
|
-
parentRunId: row.parent_run_id ?? void 0,
|
|
1120
|
-
input: typeof row.input_json === "string" ? JSON.parse(row.input_json) : row.input_json,
|
|
1121
|
-
context: typeof row.context_json === "string" ? JSON.parse(row.context_json) : row.context_json,
|
|
1122
|
-
output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
|
|
1123
|
-
error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
|
|
1124
|
-
metadata: typeof row.metadata_json === "string" ? JSON.parse(row.metadata_json) : row.metadata_json,
|
|
1125
|
-
priority: row.priority ?? 0,
|
|
1126
|
-
timeoutMs: row.timeout_ms ?? void 0,
|
|
1127
|
-
createdAt: new Date(row.created_at),
|
|
1128
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
1129
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
/**
|
|
1133
|
-
* Map a database row to an extended workflow run record.
|
|
1134
|
-
*/
|
|
1135
|
-
mapExtendedRunRow(row) {
|
|
1136
|
-
return {
|
|
1137
|
-
id: row.id,
|
|
1138
|
-
kind: row.kind,
|
|
1139
|
-
status: row.status,
|
|
1140
|
-
input: typeof row.input_json === "string" ? JSON.parse(row.input_json) : row.input_json,
|
|
1141
|
-
metadata: typeof row.metadata_json === "string" ? JSON.parse(row.metadata_json) : row.metadata_json,
|
|
1142
|
-
context: typeof row.context_json === "string" ? JSON.parse(row.context_json) : row.context_json,
|
|
1143
|
-
output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
|
|
1144
|
-
error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
|
|
1145
|
-
priority: row.priority ?? 0,
|
|
1146
|
-
timeoutMs: row.timeout_ms ?? void 0,
|
|
1147
|
-
createdAt: new Date(row.created_at),
|
|
1148
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
1149
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
1150
|
-
};
|
|
1151
|
-
}
|
|
1315
|
+
//
|
|
1316
|
+
// Mappers and JSON-parse helpers for the three core tables live in
|
|
1317
|
+
// `./postgres-core.ts`. The single mapper kept here covers
|
|
1318
|
+
// `stepflow_step_results`, the only table outside the core schema.
|
|
1152
1319
|
mapStepResultRow(row) {
|
|
1320
|
+
const ctx = this.mapperCtx;
|
|
1153
1321
|
return {
|
|
1154
1322
|
id: row.id,
|
|
1155
1323
|
runId: row.run_id,
|
|
1156
1324
|
stepName: row.step_name,
|
|
1157
1325
|
status: row.status,
|
|
1158
|
-
output:
|
|
1159
|
-
|
|
1326
|
+
output: safeParseOptionalField2(row.output_json, {
|
|
1327
|
+
...ctx,
|
|
1328
|
+
rowId: row.id,
|
|
1329
|
+
column: "output_json"
|
|
1330
|
+
}),
|
|
1331
|
+
error: safeParseOptionalField2(row.error_json, {
|
|
1332
|
+
...ctx,
|
|
1333
|
+
rowId: row.id,
|
|
1334
|
+
column: "error_json"
|
|
1335
|
+
}),
|
|
1160
1336
|
attempt: row.attempt,
|
|
1161
1337
|
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
1162
1338
|
completedAt: row.completed_at ? new Date(row.completed_at) : void 0
|
|
1163
1339
|
};
|
|
1164
1340
|
}
|
|
1165
|
-
mapStepRow(row) {
|
|
1166
|
-
return {
|
|
1167
|
-
id: row.id,
|
|
1168
|
-
runId: row.run_id,
|
|
1169
|
-
stepKey: row.step_key,
|
|
1170
|
-
stepName: row.step_name,
|
|
1171
|
-
status: row.status,
|
|
1172
|
-
attempt: row.attempt,
|
|
1173
|
-
result: row.result_json ? typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json : void 0,
|
|
1174
|
-
error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
|
|
1175
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
1176
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
mapEventRow(row) {
|
|
1180
|
-
return {
|
|
1181
|
-
id: row.id,
|
|
1182
|
-
runId: row.run_id,
|
|
1183
|
-
stepKey: row.step_key ?? void 0,
|
|
1184
|
-
eventType: row.event_type,
|
|
1185
|
-
level: row.level,
|
|
1186
|
-
payload: row.payload_json ? typeof row.payload_json === "string" ? JSON.parse(row.payload_json) : row.payload_json : void 0,
|
|
1187
|
-
timestamp: new Date(row.timestamp)
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
1341
|
// ============================================================================
|
|
1191
1342
|
// New WorkflowStorage Methods
|
|
1192
1343
|
// ============================================================================
|
|
@@ -1255,7 +1406,7 @@ var PostgresStorageAdapter = class {
|
|
|
1255
1406
|
*/
|
|
1256
1407
|
async getStepResult(runId, stepName) {
|
|
1257
1408
|
this.ensureInitialized();
|
|
1258
|
-
const row = await this.
|
|
1409
|
+
const row = await this.extQb.selectFrom("stepflow_step_results").selectAll().where("run_id", "=", runId).where("step_name", "=", stepName).executeTakeFirst();
|
|
1259
1410
|
return row ? this.mapStepResultRow(row) : void 0;
|
|
1260
1411
|
}
|
|
1261
1412
|
/**
|
|
@@ -1263,7 +1414,7 @@ var PostgresStorageAdapter = class {
|
|
|
1263
1414
|
*/
|
|
1264
1415
|
async getStepResults(runId) {
|
|
1265
1416
|
this.ensureInitialized();
|
|
1266
|
-
const rows = await this.
|
|
1417
|
+
const rows = await this.extQb.selectFrom("stepflow_step_results").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
|
|
1267
1418
|
return rows.map((row) => this.mapStepResultRow(row));
|
|
1268
1419
|
}
|
|
1269
1420
|
/**
|
|
@@ -1273,7 +1424,7 @@ var PostgresStorageAdapter = class {
|
|
|
1273
1424
|
async saveStepResult(result) {
|
|
1274
1425
|
this.ensureInitialized();
|
|
1275
1426
|
const id = result.id ?? generateId();
|
|
1276
|
-
await this.
|
|
1427
|
+
await this.extQb.insertInto("stepflow_step_results").values({
|
|
1277
1428
|
id,
|
|
1278
1429
|
run_id: result.runId,
|
|
1279
1430
|
step_name: result.stepName,
|
|
@@ -1314,199 +1465,24 @@ var PostgresStorageAdapter = class {
|
|
|
1314
1465
|
};
|
|
1315
1466
|
}
|
|
1316
1467
|
};
|
|
1317
|
-
var PostgresTransactionAdapter = class {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
this.
|
|
1322
|
-
}
|
|
1323
|
-
qb;
|
|
1324
|
-
async createRun(run) {
|
|
1325
|
-
const id = "id" in run && run.id ? run.id : generateId();
|
|
1326
|
-
const createdAt = /* @__PURE__ */ new Date();
|
|
1327
|
-
await this.qb.insertInto("runs").values({
|
|
1328
|
-
id,
|
|
1329
|
-
kind: run.kind,
|
|
1330
|
-
status: run.status,
|
|
1331
|
-
parent_run_id: "parentRunId" in run ? run.parentRunId ?? null : null,
|
|
1332
|
-
input_json: JSON.stringify(run.input),
|
|
1333
|
-
metadata_json: JSON.stringify(run.metadata ?? {}),
|
|
1334
|
-
context_json: JSON.stringify(run.context ?? {}),
|
|
1335
|
-
output_json: null,
|
|
1336
|
-
error_json: "error" in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
|
|
1337
|
-
priority: "priority" in run ? run.priority ?? 0 : 0,
|
|
1338
|
-
timeout_ms: "timeoutMs" in run ? run.timeoutMs ?? null : null,
|
|
1339
|
-
created_at: createdAt,
|
|
1340
|
-
started_at: null,
|
|
1341
|
-
finished_at: null
|
|
1342
|
-
}).execute();
|
|
1343
|
-
return {
|
|
1344
|
-
id,
|
|
1345
|
-
kind: run.kind,
|
|
1346
|
-
status: run.status,
|
|
1347
|
-
parentRunId: "parentRunId" in run ? run.parentRunId : void 0,
|
|
1348
|
-
input: run.input,
|
|
1349
|
-
context: run.context ?? {},
|
|
1350
|
-
output: void 0,
|
|
1351
|
-
error: void 0,
|
|
1352
|
-
metadata: run.metadata ?? {},
|
|
1353
|
-
priority: "priority" in run ? run.priority ?? 0 : 0,
|
|
1354
|
-
timeoutMs: "timeoutMs" in run ? run.timeoutMs : void 0,
|
|
1355
|
-
createdAt
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
async getRun(runId) {
|
|
1359
|
-
const row = await this.qb.selectFrom("runs").selectAll().where("id", "=", runId).executeTakeFirst();
|
|
1360
|
-
return row ? this.mapRunRow(row) : null;
|
|
1361
|
-
}
|
|
1362
|
-
async updateRun(runId, updates) {
|
|
1363
|
-
const updateData = {};
|
|
1364
|
-
if (updates.status !== void 0) updateData.status = updates.status;
|
|
1365
|
-
if (updates.context !== void 0)
|
|
1366
|
-
updateData.context_json = JSON.stringify(updates.context);
|
|
1367
|
-
if ("output" in updates && updates.output !== void 0)
|
|
1368
|
-
updateData.output_json = JSON.stringify(updates.output);
|
|
1369
|
-
if (updates.error !== void 0)
|
|
1370
|
-
updateData.error_json = JSON.stringify(
|
|
1371
|
-
sanitizeErrorForStorage(updates.error)
|
|
1372
|
-
);
|
|
1373
|
-
if (updates.startedAt !== void 0)
|
|
1374
|
-
updateData.started_at = updates.startedAt;
|
|
1375
|
-
if (updates.finishedAt !== void 0)
|
|
1376
|
-
updateData.finished_at = updates.finishedAt;
|
|
1377
|
-
if (Object.keys(updateData).length > 0) {
|
|
1378
|
-
await this.qb.updateTable("runs").set(updateData).where("id", "=", runId).execute();
|
|
1379
|
-
}
|
|
1468
|
+
var PostgresTransactionAdapter = class extends PostgresStorageCore {
|
|
1469
|
+
_qb;
|
|
1470
|
+
constructor(trx, schema, logger) {
|
|
1471
|
+
super(schema, logger);
|
|
1472
|
+
this._qb = trx.withSchema(schema);
|
|
1380
1473
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
const offset = options.offset ?? 0;
|
|
1384
|
-
let query = this.qb.selectFrom("runs").selectAll();
|
|
1385
|
-
if (options.kind) query = query.where("kind", "=", options.kind);
|
|
1386
|
-
if (options.status) {
|
|
1387
|
-
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
1388
|
-
query = query.where("status", "in", statuses);
|
|
1389
|
-
}
|
|
1390
|
-
if (options.parentRunId !== void 0) {
|
|
1391
|
-
query = query.where("parent_run_id", "=", options.parentRunId);
|
|
1392
|
-
}
|
|
1393
|
-
const orderColumn = (options.orderBy ?? "createdAt") === "createdAt" ? "created_at" : options.orderBy === "startedAt" ? "started_at" : "finished_at";
|
|
1394
|
-
const orderDirection = options.orderDirection ?? "desc";
|
|
1395
|
-
query = query.orderBy(orderColumn, orderDirection).limit(limit).offset(offset);
|
|
1396
|
-
const rows = await query.execute();
|
|
1397
|
-
return {
|
|
1398
|
-
items: rows.map((row) => this.mapRunRow(row)),
|
|
1399
|
-
total: rows.length
|
|
1400
|
-
// Simplified for transaction context
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
async createStep(step) {
|
|
1404
|
-
const id = generateId();
|
|
1405
|
-
await this.qb.insertInto("workflow_run_steps").values({
|
|
1406
|
-
id,
|
|
1407
|
-
run_id: step.runId,
|
|
1408
|
-
step_key: step.stepKey,
|
|
1409
|
-
step_name: step.stepName,
|
|
1410
|
-
status: step.status,
|
|
1411
|
-
attempt: step.attempt,
|
|
1412
|
-
result_json: step.result !== void 0 ? JSON.stringify(step.result) : null,
|
|
1413
|
-
error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
|
|
1414
|
-
started_at: step.startedAt ?? null,
|
|
1415
|
-
finished_at: step.finishedAt ?? null
|
|
1416
|
-
}).execute();
|
|
1417
|
-
return { ...step, id };
|
|
1418
|
-
}
|
|
1419
|
-
async getStep(stepId) {
|
|
1420
|
-
const row = await this.qb.selectFrom("workflow_run_steps").selectAll().where("id", "=", stepId).executeTakeFirst();
|
|
1421
|
-
return row ? this.mapStepRow(row) : null;
|
|
1422
|
-
}
|
|
1423
|
-
async updateStep(stepId, updates) {
|
|
1424
|
-
const updateData = {};
|
|
1425
|
-
if (updates.status !== void 0) updateData.status = updates.status;
|
|
1426
|
-
if (updates.attempt !== void 0) updateData.attempt = updates.attempt;
|
|
1427
|
-
if (updates.result !== void 0)
|
|
1428
|
-
updateData.result_json = JSON.stringify(updates.result);
|
|
1429
|
-
if (updates.error !== void 0)
|
|
1430
|
-
updateData.error_json = JSON.stringify(
|
|
1431
|
-
sanitizeErrorForStorage(updates.error)
|
|
1432
|
-
);
|
|
1433
|
-
if (updates.finishedAt !== void 0)
|
|
1434
|
-
updateData.finished_at = updates.finishedAt;
|
|
1435
|
-
if (Object.keys(updateData).length > 0) {
|
|
1436
|
-
await this.qb.updateTable("workflow_run_steps").set(updateData).where("id", "=", stepId).execute();
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
async getStepsForRun(runId) {
|
|
1440
|
-
const rows = await this.qb.selectFrom("workflow_run_steps").selectAll().where("run_id", "=", runId).orderBy("started_at", "asc").execute();
|
|
1441
|
-
return rows.map((row) => this.mapStepRow(row));
|
|
1442
|
-
}
|
|
1443
|
-
async saveEvent(event) {
|
|
1444
|
-
const id = generateId();
|
|
1445
|
-
await this.qb.insertInto("workflow_events").values({
|
|
1446
|
-
id,
|
|
1447
|
-
run_id: event.runId,
|
|
1448
|
-
step_key: event.stepKey ?? null,
|
|
1449
|
-
event_type: event.eventType,
|
|
1450
|
-
level: event.level,
|
|
1451
|
-
payload_json: event.payload !== void 0 ? JSON.stringify(event.payload) : null,
|
|
1452
|
-
timestamp: event.timestamp
|
|
1453
|
-
}).execute();
|
|
1454
|
-
}
|
|
1455
|
-
async getEventsForRun(runId, options = {}) {
|
|
1456
|
-
let query = this.qb.selectFrom("workflow_events").selectAll().where("run_id", "=", runId);
|
|
1457
|
-
if (options.stepKey) query = query.where("step_key", "=", options.stepKey);
|
|
1458
|
-
if (options.level) query = query.where("level", "=", options.level);
|
|
1459
|
-
const rows = await query.orderBy("timestamp", "asc").limit(options.limit ?? 1e3).offset(options.offset ?? 0).execute();
|
|
1460
|
-
return rows.map((row) => this.mapEventRow(row));
|
|
1461
|
-
}
|
|
1462
|
-
mapRunRow(row) {
|
|
1463
|
-
return {
|
|
1464
|
-
id: row.id,
|
|
1465
|
-
kind: row.kind,
|
|
1466
|
-
status: row.status,
|
|
1467
|
-
parentRunId: row.parent_run_id ?? void 0,
|
|
1468
|
-
input: typeof row.input_json === "string" ? JSON.parse(row.input_json) : row.input_json,
|
|
1469
|
-
context: typeof row.context_json === "string" ? JSON.parse(row.context_json) : row.context_json,
|
|
1470
|
-
output: row.output_json ? typeof row.output_json === "string" ? JSON.parse(row.output_json) : row.output_json : void 0,
|
|
1471
|
-
error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
|
|
1472
|
-
metadata: typeof row.metadata_json === "string" ? JSON.parse(row.metadata_json) : row.metadata_json,
|
|
1473
|
-
priority: row.priority ?? 0,
|
|
1474
|
-
timeoutMs: row.timeout_ms ?? void 0,
|
|
1475
|
-
createdAt: new Date(row.created_at),
|
|
1476
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
1477
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
mapStepRow(row) {
|
|
1481
|
-
return {
|
|
1482
|
-
id: row.id,
|
|
1483
|
-
runId: row.run_id,
|
|
1484
|
-
stepKey: row.step_key,
|
|
1485
|
-
stepName: row.step_name,
|
|
1486
|
-
status: row.status,
|
|
1487
|
-
attempt: row.attempt,
|
|
1488
|
-
result: row.result_json ? typeof row.result_json === "string" ? JSON.parse(row.result_json) : row.result_json : void 0,
|
|
1489
|
-
error: row.error_json ? typeof row.error_json === "string" ? JSON.parse(row.error_json) : row.error_json : void 0,
|
|
1490
|
-
startedAt: row.started_at ? new Date(row.started_at) : void 0,
|
|
1491
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : void 0
|
|
1492
|
-
};
|
|
1474
|
+
get qb() {
|
|
1475
|
+
return this._qb;
|
|
1493
1476
|
}
|
|
1494
|
-
|
|
1495
|
-
return
|
|
1496
|
-
id: row.id,
|
|
1497
|
-
runId: row.run_id,
|
|
1498
|
-
stepKey: row.step_key ?? void 0,
|
|
1499
|
-
eventType: row.event_type,
|
|
1500
|
-
level: row.level,
|
|
1501
|
-
payload: row.payload_json ? typeof row.payload_json === "string" ? JSON.parse(row.payload_json) : row.payload_json : void 0,
|
|
1502
|
-
timestamp: new Date(row.timestamp)
|
|
1503
|
-
};
|
|
1477
|
+
get warnLabel() {
|
|
1478
|
+
return "PostgresTransactionAdapter";
|
|
1504
1479
|
}
|
|
1505
1480
|
};
|
|
1506
1481
|
|
|
1507
1482
|
export {
|
|
1508
1483
|
MemoryStorageAdapter,
|
|
1509
1484
|
SQLiteStorageAdapter,
|
|
1510
|
-
PostgresStorageAdapter
|
|
1485
|
+
PostgresStorageAdapter,
|
|
1486
|
+
PostgresTransactionAdapter
|
|
1511
1487
|
};
|
|
1512
|
-
//# sourceMappingURL=chunk-
|
|
1488
|
+
//# sourceMappingURL=chunk-5XJBAPXF.js.map
|