@multiplier-labs/stepflow 0.3.3 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-GYPNJBDP.js → chunk-D5HW7VEG.js} +269 -48
  3. package/dist/chunk-D5HW7VEG.js.map +1 -0
  4. package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
  5. package/dist/chunk-FMPJBK2N.js.map +1 -0
  6. package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
  7. package/dist/chunk-JPXPB7U5.js.map +1 -0
  8. package/dist/{chunk-4VOKMGP6.js → chunk-JUPZEBR3.js} +519 -522
  9. package/dist/chunk-JUPZEBR3.js.map +1 -0
  10. package/dist/events/index.d.ts +58 -3
  11. package/dist/events/index.js +1 -1
  12. package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
  13. package/dist/index.d.ts +2 -12
  14. package/dist/index.js +764 -485
  15. package/dist/index.js.map +1 -1
  16. package/dist/scheduler/index.d.ts +1 -1
  17. package/dist/scheduler/index.js +2 -2
  18. package/dist/storage/index.d.ts +313 -47
  19. package/dist/storage/index.js +4 -2
  20. package/package.json +27 -13
  21. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  22. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  23. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  24. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  25. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  26. package/.github/CODEOWNERS +0 -15
  27. package/.github/dependabot.yml +0 -23
  28. package/.github/workflows/ci.yml +0 -38
  29. package/.github/workflows/publish.yml +0 -36
  30. package/.github/workflows/security.yml +0 -29
  31. package/CHANGELOG.md +0 -34
  32. package/SECURITY.md +0 -40
  33. package/dist/chunk-4VOKMGP6.js.map +0 -1
  34. package/dist/chunk-GYPNJBDP.js.map +0 -1
  35. package/dist/chunk-KHTUWLVZ.js.map +0 -1
  36. package/dist/chunk-ML35PIHX.js.map +0 -1
  37. package/docs/github-packages-setup.md +0 -97
  38. package/docs/stepflow-api-reference.md +0 -1661
  39. package/src/core/engine.test.ts +0 -1365
  40. package/src/core/engine.ts +0 -692
  41. package/src/core/index.ts +0 -7
  42. package/src/core/orchestrator.ts +0 -571
  43. package/src/core/types.ts +0 -220
  44. package/src/events/index.ts +0 -8
  45. package/src/events/memory.test.ts +0 -107
  46. package/src/events/memory.ts +0 -92
  47. package/src/events/socketio.test.ts +0 -378
  48. package/src/events/socketio.ts +0 -262
  49. package/src/events/types.ts +0 -118
  50. package/src/events/webhook.test.ts +0 -657
  51. package/src/events/webhook.ts +0 -564
  52. package/src/index.ts +0 -270
  53. package/src/planning/index.ts +0 -55
  54. package/src/planning/planner.ts +0 -576
  55. package/src/planning/planning.test.ts +0 -727
  56. package/src/planning/registry.test.ts +0 -398
  57. package/src/planning/registry.ts +0 -365
  58. package/src/planning/types.ts +0 -566
  59. package/src/scheduler/cron.test.ts +0 -471
  60. package/src/scheduler/cron.ts +0 -414
  61. package/src/scheduler/index.ts +0 -8
  62. package/src/scheduler/postgres-persistence.ts +0 -518
  63. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  64. package/src/scheduler/sqlite-persistence.ts +0 -261
  65. package/src/scheduler/types.ts +0 -73
  66. package/src/storage/index.ts +0 -27
  67. package/src/storage/memory.test.ts +0 -349
  68. package/src/storage/memory.ts +0 -227
  69. package/src/storage/postgres.ts +0 -1354
  70. package/src/storage/sqlite.test.ts +0 -466
  71. package/src/storage/sqlite.ts +0 -627
  72. package/src/storage/types.ts +0 -343
  73. package/src/utils/errors.test.ts +0 -150
  74. package/src/utils/errors.ts +0 -161
  75. package/src/utils/id.ts +0 -21
  76. package/src/utils/index.ts +0 -8
  77. package/src/utils/logger.test.ts +0 -121
  78. package/src/utils/logger.ts +0 -96
  79. package/src/utils/postgres-deps.ts +0 -63
  80. package/src/utils/retry.test.ts +0 -177
  81. package/src/utils/retry.ts +0 -121
  82. package/tsconfig.json +0 -21
  83. package/tsup.config.ts +0 -14
  84. package/vitest.config.ts +0 -9
@@ -1,627 +0,0 @@
1
- /**
2
- * SQLite storage adapter using better-sqlite3.
3
- *
4
- * Provides durable persistence for workflow runs, steps, and events.
5
- */
6
-
7
- import type Database from 'better-sqlite3';
8
- import { generateId } from '../utils/id';
9
- import type {
10
- StorageAdapter,
11
- WorkflowRunRecord,
12
- WorkflowRunStepRecord,
13
- WorkflowEventRecord,
14
- ListRunsOptions,
15
- ListEventsOptions,
16
- PaginatedResult,
17
- } from './types';
18
- import type { RunStatus, StepStatus, WorkflowError } from '../core/types';
19
- import { sanitizeErrorForStorage } from '../utils/logger';
20
-
21
- // ============================================================================
22
- // Schema SQL
23
- // ============================================================================
24
-
25
- /**
26
- * SQL statements to create the workflow tables.
27
- * These create the workflow tables in your database.
28
- */
29
- const CREATE_TABLES_SQL = `
30
- -- Workflow runs table
31
- CREATE TABLE IF NOT EXISTS workflow_runs (
32
- id TEXT PRIMARY KEY,
33
- kind TEXT NOT NULL,
34
- status TEXT NOT NULL,
35
- parent_run_id TEXT,
36
- input_json TEXT NOT NULL,
37
- metadata_json TEXT NOT NULL,
38
- context_json TEXT NOT NULL,
39
- completed_steps_json TEXT,
40
- error_json TEXT,
41
- created_at TEXT NOT NULL,
42
- started_at TEXT,
43
- finished_at TEXT
44
- );
45
-
46
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_kind_status ON workflow_runs(kind, status);
47
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_parent ON workflow_runs(parent_run_id);
48
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_created ON workflow_runs(created_at);
49
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_status ON workflow_runs(status);
50
-
51
- -- Workflow run steps table
52
- CREATE TABLE IF NOT EXISTS workflow_run_steps (
53
- id TEXT PRIMARY KEY,
54
- run_id TEXT NOT NULL,
55
- step_key TEXT NOT NULL,
56
- step_name TEXT,
57
- status TEXT NOT NULL,
58
- attempt INTEGER NOT NULL DEFAULT 1,
59
- result_json TEXT,
60
- error_json TEXT,
61
- started_at TEXT,
62
- finished_at TEXT,
63
- FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
64
- );
65
-
66
- CREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run ON workflow_run_steps(run_id);
67
- CREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run_key ON workflow_run_steps(run_id, step_key);
68
-
69
- -- Workflow events table
70
- CREATE TABLE IF NOT EXISTS workflow_events (
71
- id TEXT PRIMARY KEY,
72
- run_id TEXT NOT NULL,
73
- step_key TEXT,
74
- event_type TEXT NOT NULL,
75
- level TEXT NOT NULL,
76
- payload_json TEXT,
77
- timestamp TEXT NOT NULL,
78
- FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
79
- );
80
-
81
- CREATE INDEX IF NOT EXISTS idx_workflow_events_run ON workflow_events(run_id);
82
- CREATE INDEX IF NOT EXISTS idx_workflow_events_run_ts ON workflow_events(run_id, timestamp);
83
- `;
84
-
85
- // ============================================================================
86
- // Configuration
87
- // ============================================================================
88
-
89
- /**
90
- * Configuration options for the SQLite storage adapter.
91
- */
92
- export interface SQLiteStorageConfig {
93
- /**
94
- * The better-sqlite3 database instance.
95
- * Can be an in-memory database (`:memory:`) or a file path.
96
- */
97
- db: Database.Database;
98
-
99
- /**
100
- * Whether to automatically create tables on initialization.
101
- * Default: true
102
- */
103
- autoCreateTables?: boolean;
104
-
105
- /**
106
- * @deprecated This option is not currently supported and will be ignored.
107
- * Table names are always prefixed with 'workflow_'.
108
- */
109
- tablePrefix?: string;
110
- }
111
-
112
- // ============================================================================
113
- // SQLite Storage Adapter
114
- // ============================================================================
115
-
116
- /**
117
- * SQLite implementation of StorageAdapter using better-sqlite3.
118
- *
119
- * Features:
120
- * - Synchronous operations (better-sqlite3 is sync by design)
121
- * - Automatic table creation
122
- * - Transaction support
123
- * - Customizable table prefix
124
- *
125
- * @example
126
- * ```typescript
127
- * import Database from 'better-sqlite3';
128
- * import { SQLiteStorageAdapter } from 'stepflow/storage';
129
- *
130
- * const db = new Database('./workflows.db');
131
- * const storage = new SQLiteStorageAdapter({ db });
132
- *
133
- * const engine = new WorkflowEngine({ storage });
134
- * ```
135
- */
136
- export class SQLiteStorageAdapter implements StorageAdapter {
137
- private db: Database.Database;
138
- private prefix: string;
139
-
140
- // Prepared statements (cached for performance)
141
- private stmts: {
142
- insertRun: Database.Statement;
143
- getRun: Database.Statement;
144
- updateRun: Database.Statement;
145
- listRuns: Database.Statement;
146
- countRuns: Database.Statement;
147
- insertStep: Database.Statement;
148
- getStep: Database.Statement;
149
- updateStep: Database.Statement;
150
- getStepsForRun: Database.Statement;
151
- insertEvent: Database.Statement;
152
- getEventsForRun: Database.Statement;
153
- deleteOldRuns: Database.Statement;
154
- deleteStepsForRuns: Database.Statement;
155
- deleteEventsForRuns: Database.Statement;
156
- getInterruptedRuns: Database.Statement;
157
- getLastCompletedStep: Database.Statement;
158
- } | null = null;
159
-
160
- constructor(config: SQLiteStorageConfig) {
161
- this.db = config.db;
162
- this.prefix = config.tablePrefix ?? 'workflow';
163
-
164
- // Enable foreign keys
165
- this.db.pragma('foreign_keys = ON');
166
-
167
- // Create tables if needed
168
- if (config.autoCreateTables !== false) {
169
- this.createTables();
170
- }
171
-
172
- // Prepare statements
173
- this.prepareStatements();
174
- }
175
-
176
- /**
177
- * Create the workflow tables if they don't exist.
178
- */
179
- private createTables(): void {
180
- this.db.exec(CREATE_TABLES_SQL);
181
- }
182
-
183
- /**
184
- * Prepare all SQL statements for better performance.
185
- */
186
- private prepareStatements(): void {
187
- this.stmts = {
188
- insertRun: this.db.prepare(`
189
- 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)
190
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
191
- `),
192
- getRun: this.db.prepare(`
193
- SELECT * FROM workflow_runs WHERE id = ?
194
- `),
195
- updateRun: this.db.prepare(`
196
- UPDATE workflow_runs
197
- SET status = COALESCE(?, status),
198
- context_json = COALESCE(?, context_json),
199
- completed_steps_json = COALESCE(?, completed_steps_json),
200
- error_json = COALESCE(?, error_json),
201
- started_at = COALESCE(?, started_at),
202
- finished_at = COALESCE(?, finished_at)
203
- WHERE id = ?
204
- `),
205
- // json_each() is a SQLite table-valued function that expands a JSON array into rows.
206
- // We use it here to support filtering by multiple statuses in a single prepared statement:
207
- // the caller passes a JSON array like '["running","failed"]', and json_each unpacks it
208
- // so the IN clause can match against each value. When status is NULL, the entire
209
- // condition is bypassed via the (? IS NULL OR ...) guard.
210
- listRuns: this.db.prepare(`
211
- SELECT * FROM workflow_runs
212
- WHERE (? IS NULL OR kind = ?)
213
- AND (? IS NULL OR status IN (SELECT value FROM json_each(?)))
214
- AND (? IS NULL OR parent_run_id = ?)
215
- ORDER BY created_at DESC
216
- LIMIT ? OFFSET ?
217
- `),
218
- countRuns: this.db.prepare(`
219
- SELECT COUNT(*) as count FROM workflow_runs
220
- WHERE (? IS NULL OR kind = ?)
221
- AND (? IS NULL OR status IN (SELECT value FROM json_each(?)))
222
- AND (? IS NULL OR parent_run_id = ?)
223
- `),
224
- insertStep: this.db.prepare(`
225
- INSERT INTO workflow_run_steps (id, run_id, step_key, step_name, status, attempt, result_json, error_json, started_at, finished_at)
226
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
227
- `),
228
- getStep: this.db.prepare(`
229
- SELECT * FROM workflow_run_steps WHERE id = ?
230
- `),
231
- updateStep: this.db.prepare(`
232
- UPDATE workflow_run_steps
233
- SET status = COALESCE(?, status),
234
- attempt = COALESCE(?, attempt),
235
- result_json = COALESCE(?, result_json),
236
- error_json = COALESCE(?, error_json),
237
- finished_at = COALESCE(?, finished_at)
238
- WHERE id = ?
239
- `),
240
- getStepsForRun: this.db.prepare(`
241
- SELECT * FROM workflow_run_steps WHERE run_id = ? ORDER BY started_at ASC
242
- `),
243
- insertEvent: this.db.prepare(`
244
- INSERT INTO workflow_events (id, run_id, step_key, event_type, level, payload_json, timestamp)
245
- VALUES (?, ?, ?, ?, ?, ?, ?)
246
- `),
247
- getEventsForRun: this.db.prepare(`
248
- SELECT * FROM workflow_events
249
- WHERE run_id = ?
250
- AND (? IS NULL OR step_key = ?)
251
- AND (? IS NULL OR level = ?)
252
- ORDER BY timestamp ASC
253
- LIMIT ? OFFSET ?
254
- `),
255
- deleteOldRuns: this.db.prepare(`
256
- DELETE FROM workflow_runs WHERE created_at < ?
257
- `),
258
- deleteStepsForRuns: this.db.prepare(`
259
- DELETE FROM workflow_run_steps WHERE run_id IN (SELECT id FROM workflow_runs WHERE created_at < ?)
260
- `),
261
- deleteEventsForRuns: this.db.prepare(`
262
- DELETE FROM workflow_events WHERE run_id IN (SELECT id FROM workflow_runs WHERE created_at < ?)
263
- `),
264
- getInterruptedRuns: this.db.prepare(`
265
- SELECT * FROM workflow_runs
266
- WHERE status IN ('queued', 'running')
267
- ORDER BY created_at ASC
268
- `),
269
- getLastCompletedStep: this.db.prepare(`
270
- SELECT * FROM workflow_run_steps
271
- WHERE run_id = ? AND status = 'succeeded'
272
- ORDER BY finished_at DESC
273
- LIMIT 1
274
- `),
275
- };
276
- }
277
-
278
- // ============================================================================
279
- // Run Operations
280
- // ============================================================================
281
-
282
- async createRun(run: Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {
283
- const id = generateId();
284
- const createdAt = new Date();
285
-
286
- this.stmts!.insertRun.run(
287
- id,
288
- run.kind,
289
- run.status,
290
- run.parentRunId ?? null,
291
- JSON.stringify(run.input),
292
- JSON.stringify(run.metadata),
293
- JSON.stringify(run.context),
294
- run.completedSteps ? JSON.stringify(run.completedSteps) : null,
295
- run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
296
- createdAt.toISOString(),
297
- run.startedAt?.toISOString() ?? null,
298
- run.finishedAt?.toISOString() ?? null
299
- );
300
-
301
- return {
302
- ...run,
303
- id,
304
- createdAt,
305
- };
306
- }
307
-
308
- async getRun(runId: string): Promise<WorkflowRunRecord | null> {
309
- const row = this.stmts!.getRun.get(runId) as SQLiteRunRow | undefined;
310
- return row ? this.mapRunRow(row) : null;
311
- }
312
-
313
- async updateRun(runId: string, updates: Partial<WorkflowRunRecord>): Promise<void> {
314
- this.stmts!.updateRun.run(
315
- updates.status ?? null,
316
- updates.context ? JSON.stringify(updates.context) : null,
317
- updates.completedSteps ? JSON.stringify(updates.completedSteps) : null,
318
- updates.error ? JSON.stringify(sanitizeErrorForStorage(updates.error)) : null,
319
- updates.startedAt?.toISOString() ?? null,
320
- updates.finishedAt?.toISOString() ?? null,
321
- runId
322
- );
323
- }
324
-
325
- async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {
326
- const limit = options.limit ?? 50;
327
- const offset = options.offset ?? 0;
328
- const kind = options.kind ?? null;
329
- const status = options.status ?? null;
330
- const statusJson = status ? JSON.stringify(Array.isArray(status) ? status : [status]) : null;
331
- const parentRunId = options.parentRunId ?? null;
332
-
333
- const rows = this.stmts!.listRuns.all(
334
- kind, kind,
335
- statusJson, statusJson,
336
- parentRunId, parentRunId,
337
- limit, offset
338
- ) as SQLiteRunRow[];
339
-
340
- const countResult = this.stmts!.countRuns.get(
341
- kind, kind,
342
- statusJson, statusJson,
343
- parentRunId, parentRunId
344
- ) as { count: number };
345
-
346
- return {
347
- items: rows.map(row => this.mapRunRow(row)),
348
- total: countResult.count,
349
- limit,
350
- offset,
351
- };
352
- }
353
-
354
- // ============================================================================
355
- // Step Operations
356
- // ============================================================================
357
-
358
- async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {
359
- const id = generateId();
360
-
361
- this.stmts!.insertStep.run(
362
- id,
363
- step.runId,
364
- step.stepKey,
365
- step.stepName,
366
- step.status,
367
- step.attempt,
368
- step.result !== undefined ? JSON.stringify(step.result) : null,
369
- step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
370
- step.startedAt?.toISOString() ?? null,
371
- step.finishedAt?.toISOString() ?? null
372
- );
373
-
374
- return { ...step, id };
375
- }
376
-
377
- async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {
378
- const row = this.stmts!.getStep.get(stepId) as SQLiteStepRow | undefined;
379
- return row ? this.mapStepRow(row) : null;
380
- }
381
-
382
- async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {
383
- this.stmts!.updateStep.run(
384
- updates.status ?? null,
385
- updates.attempt ?? null,
386
- updates.result !== undefined ? JSON.stringify(updates.result) : null,
387
- updates.error ? JSON.stringify(sanitizeErrorForStorage(updates.error)) : null,
388
- updates.finishedAt?.toISOString() ?? null,
389
- stepId
390
- );
391
- }
392
-
393
- async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {
394
- const rows = this.stmts!.getStepsForRun.all(runId) as SQLiteStepRow[];
395
- return rows.map(row => this.mapStepRow(row));
396
- }
397
-
398
- // ============================================================================
399
- // Event Operations
400
- // ============================================================================
401
-
402
- async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {
403
- const id = generateId();
404
-
405
- this.stmts!.insertEvent.run(
406
- id,
407
- event.runId,
408
- event.stepKey ?? null,
409
- event.eventType,
410
- event.level,
411
- event.payload !== undefined ? JSON.stringify(event.payload) : null,
412
- event.timestamp.toISOString()
413
- );
414
- }
415
-
416
- async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {
417
- const limit = options.limit ?? 1000;
418
- const offset = options.offset ?? 0;
419
- const stepKey = options.stepKey ?? null;
420
- const level = options.level ?? null;
421
-
422
- const rows = this.stmts!.getEventsForRun.all(
423
- runId,
424
- stepKey, stepKey,
425
- level, level,
426
- limit, offset
427
- ) as SQLiteEventRow[];
428
-
429
- return rows.map(row => this.mapEventRow(row));
430
- }
431
-
432
- // ============================================================================
433
- // Transaction Support
434
- // ============================================================================
435
-
436
- /**
437
- * Execute a function within a database transaction (async interface).
438
- *
439
- * @deprecated Use `transactionSync()` instead. This method only works when
440
- * the callback performs purely synchronous operations wrapped in async/await.
441
- * If the callback awaits real async I/O (network, timers, etc.), it will
442
- * throw an error. `transactionSync()` makes the synchronous requirement explicit.
443
- */
444
- async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {
445
- let fnPromise!: Promise<T>;
446
- this.transactionSync(() => {
447
- fnPromise = fn(this);
448
- });
449
- return fnPromise;
450
- }
451
-
452
- /**
453
- * Execute a synchronous transaction (preferred for better-sqlite3).
454
- * Use this when you need transaction guarantees.
455
- *
456
- * @example
457
- * ```typescript
458
- * storage.transactionSync(() => {
459
- * // Multiple operations in a single transaction
460
- * const run = storage.createRunSync(...);
461
- * storage.updateRunSync(run.id, ...);
462
- * });
463
- * ```
464
- */
465
- transactionSync<T>(fn: () => T): T {
466
- return this.db.transaction(fn)();
467
- }
468
-
469
- // ============================================================================
470
- // Cleanup Operations
471
- // ============================================================================
472
-
473
- /**
474
- * Delete runs older than the specified date.
475
- * Also deletes associated steps and events (via CASCADE).
476
- */
477
- async deleteOldRuns(olderThan: Date): Promise<number> {
478
- const isoDate = olderThan.toISOString();
479
-
480
- // Due to CASCADE, steps and events are deleted automatically
481
- // But we do it explicitly for databases without CASCADE support
482
- return this.db.transaction(() => {
483
- this.stmts!.deleteEventsForRuns.run(isoDate);
484
- this.stmts!.deleteStepsForRuns.run(isoDate);
485
- const result = this.stmts!.deleteOldRuns.run(isoDate);
486
- return result.changes;
487
- })();
488
- }
489
-
490
- // ============================================================================
491
- // Resume Support
492
- // ============================================================================
493
-
494
- /**
495
- * Get all runs that were interrupted (status is 'running' or 'queued').
496
- * These runs can potentially be resumed.
497
- */
498
- async getInterruptedRuns(): Promise<WorkflowRunRecord[]> {
499
- const rows = this.stmts!.getInterruptedRuns.all() as SQLiteRunRow[];
500
- return rows.map(row => this.mapRunRow(row));
501
- }
502
-
503
- /**
504
- * Get the last completed step for a run.
505
- * Useful for resuming from a checkpoint.
506
- */
507
- async getLastCompletedStep(runId: string): Promise<WorkflowRunStepRecord | null> {
508
- const row = this.stmts!.getLastCompletedStep.get(runId) as SQLiteStepRow | undefined;
509
- return row ? this.mapStepRow(row) : null;
510
- }
511
-
512
- // ============================================================================
513
- // Row Mapping
514
- // ============================================================================
515
-
516
- private mapRunRow(row: SQLiteRunRow): WorkflowRunRecord {
517
- return {
518
- id: row.id,
519
- kind: row.kind,
520
- status: row.status as RunStatus,
521
- parentRunId: row.parent_run_id ?? undefined,
522
- input: JSON.parse(row.input_json),
523
- metadata: JSON.parse(row.metadata_json),
524
- context: JSON.parse(row.context_json),
525
- completedSteps: row.completed_steps_json ? JSON.parse(row.completed_steps_json) : undefined,
526
- error: row.error_json ? JSON.parse(row.error_json) : undefined,
527
- createdAt: new Date(row.created_at),
528
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
529
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
530
- };
531
- }
532
-
533
- private mapStepRow(row: SQLiteStepRow): WorkflowRunStepRecord {
534
- return {
535
- id: row.id,
536
- runId: row.run_id,
537
- stepKey: row.step_key,
538
- stepName: row.step_name,
539
- status: row.status as StepStatus,
540
- attempt: row.attempt,
541
- result: row.result_json ? JSON.parse(row.result_json) : undefined,
542
- error: row.error_json ? JSON.parse(row.error_json) : undefined,
543
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
544
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
545
- };
546
- }
547
-
548
- private mapEventRow(row: SQLiteEventRow): WorkflowEventRecord {
549
- return {
550
- id: row.id,
551
- runId: row.run_id,
552
- stepKey: row.step_key ?? undefined,
553
- eventType: row.event_type,
554
- level: row.level as 'info' | 'warn' | 'error',
555
- payload: row.payload_json ? JSON.parse(row.payload_json) : undefined,
556
- timestamp: new Date(row.timestamp),
557
- };
558
- }
559
-
560
- // ============================================================================
561
- // Utility Methods
562
- // ============================================================================
563
-
564
- /**
565
- * Close the database connection.
566
- */
567
- close(): void {
568
- this.db.close();
569
- }
570
-
571
- /**
572
- * Get database statistics.
573
- */
574
- getStats(): { runs: number; steps: number; events: number } {
575
- const runsCount = this.db.prepare('SELECT COUNT(*) as count FROM workflow_runs').get() as { count: number };
576
- const stepsCount = this.db.prepare('SELECT COUNT(*) as count FROM workflow_run_steps').get() as { count: number };
577
- const eventsCount = this.db.prepare('SELECT COUNT(*) as count FROM workflow_events').get() as { count: number };
578
-
579
- return {
580
- runs: runsCount.count,
581
- steps: stepsCount.count,
582
- events: eventsCount.count,
583
- };
584
- }
585
- }
586
-
587
- // ============================================================================
588
- // SQLite Row Types
589
- // ============================================================================
590
-
591
- interface SQLiteRunRow {
592
- id: string;
593
- kind: string;
594
- status: string;
595
- parent_run_id: string | null;
596
- input_json: string;
597
- metadata_json: string;
598
- context_json: string;
599
- completed_steps_json: string | null;
600
- error_json: string | null;
601
- created_at: string;
602
- started_at: string | null;
603
- finished_at: string | null;
604
- }
605
-
606
- interface SQLiteStepRow {
607
- id: string;
608
- run_id: string;
609
- step_key: string;
610
- step_name: string;
611
- status: string;
612
- attempt: number;
613
- result_json: string | null;
614
- error_json: string | null;
615
- started_at: string | null;
616
- finished_at: string | null;
617
- }
618
-
619
- interface SQLiteEventRow {
620
- id: string;
621
- run_id: string;
622
- step_key: string | null;
623
- event_type: string;
624
- level: string;
625
- payload_json: string | null;
626
- timestamp: string;
627
- }