@multiplier-labs/stepflow 0.3.1 → 0.3.3
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/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +11 -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-4VOKMGP6.js} +23 -42
- package/dist/chunk-4VOKMGP6.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-GYPNJBDP.js} +12 -16
- package/dist/chunk-GYPNJBDP.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-KHTUWLVZ.js} +14 -47
- package/dist/chunk-KHTUWLVZ.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-ML35PIHX.js} +1 -1
- package/dist/chunk-ML35PIHX.js.map +1 -0
- package/dist/events/index.d.ts +4 -4
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Dzr437iW.d.ts} +6 -6
- package/dist/index.d.ts +14 -12
- package/dist/index.js +40 -82
- 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 +13 -13
- package/dist/storage/index.js +2 -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 +2 -3
- package/package.json +7 -6
- package/src/core/engine.test.ts +466 -522
- package/src/core/engine.ts +42 -67
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator.ts +64 -83
- 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 +197 -235
- package/src/events/webhook.ts +44 -71
- package/src/index.ts +19 -19
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +66 -91
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +39 -43
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +141 -150
- package/src/scheduler/cron.ts +32 -67
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +71 -99
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +12 -19
- 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.ts +197 -393
- package/src/storage/sqlite.test.ts +154 -281
- package/src/storage/sqlite.ts +50 -85
- 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 +4 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/postgres-deps.ts +4 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- 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
package/src/storage/postgres.ts
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* with support for distributed deployments and connection pooling.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Kysely as KyselyType } from
|
|
9
|
-
import type { Pool, PoolConfig } from
|
|
10
|
-
import { loadPostgresDeps } from
|
|
8
|
+
import type { Kysely as KyselyType } from 'kysely';
|
|
9
|
+
import type { Pool, PoolConfig } from 'pg';
|
|
10
|
+
import { loadPostgresDeps } from '../utils/postgres-deps.js';
|
|
11
11
|
|
|
12
12
|
// Lazy-loaded dependencies - populated by loadPostgresDeps() during initialize()
|
|
13
13
|
let Kysely: any;
|
|
14
14
|
let PostgresDialect: any;
|
|
15
15
|
let sql: any;
|
|
16
16
|
let pgModule: any;
|
|
17
|
-
import { generateId } from
|
|
17
|
+
import { generateId } from '../utils/id.js';
|
|
18
18
|
import type {
|
|
19
19
|
StorageAdapter,
|
|
20
20
|
WorkflowRunRecord,
|
|
@@ -29,16 +29,14 @@ import type {
|
|
|
29
29
|
ExtendedWorkflowRunRecord,
|
|
30
30
|
ExtendedRunStatus,
|
|
31
31
|
ExtendedStepStatus,
|
|
32
|
-
} from
|
|
33
|
-
import type { RunStatus, StepStatus, WorkflowError } from
|
|
34
|
-
import { sanitizeErrorForStorage } from
|
|
32
|
+
} from './types.js';
|
|
33
|
+
import type { RunStatus, StepStatus, WorkflowError } from '../core/types.js';
|
|
34
|
+
import { sanitizeErrorForStorage } from '../utils/logger.js';
|
|
35
35
|
|
|
36
36
|
/** Strip `stack` from a generic error record before persistence. */
|
|
37
|
-
function stripStack<T extends Record<string, unknown>>(
|
|
38
|
-
error: T,
|
|
39
|
-
): Omit<T, "stack"> {
|
|
37
|
+
function stripStack<T extends Record<string, unknown>>(error: T): Omit<T, 'stack'> {
|
|
40
38
|
const { stack: _stack, ...rest } = error;
|
|
41
|
-
return rest as Omit<T,
|
|
39
|
+
return rest as Omit<T, 'stack'>;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
// ============================================================================
|
|
@@ -226,11 +224,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
226
224
|
private config: PostgresStorageConfig;
|
|
227
225
|
|
|
228
226
|
constructor(config: PostgresStorageConfig) {
|
|
229
|
-
this.schema = config.schema ??
|
|
227
|
+
this.schema = config.schema ?? 'public';
|
|
230
228
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {
|
|
231
229
|
throw new Error(
|
|
232
230
|
`Invalid schema name "${this.schema}". Schema must start with a letter or underscore, ` +
|
|
233
|
-
|
|
231
|
+
`contain only alphanumeric characters and underscores, and be at most 63 characters.`
|
|
234
232
|
);
|
|
235
233
|
}
|
|
236
234
|
this.autoMigrate = config.autoMigrate !== false;
|
|
@@ -248,7 +246,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
248
246
|
private ensureInitialized(): void {
|
|
249
247
|
if (!this.initialized) {
|
|
250
248
|
throw new Error(
|
|
251
|
-
|
|
249
|
+
'PostgresStorageAdapter is not initialized. Call initialize() before using the adapter.'
|
|
252
250
|
);
|
|
253
251
|
}
|
|
254
252
|
}
|
|
@@ -272,16 +270,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
272
270
|
this.pool = this.config.pool;
|
|
273
271
|
this.ownsPool = false;
|
|
274
272
|
} else if (this.config.connectionString) {
|
|
275
|
-
this.pool = new pgModule.Pool({
|
|
276
|
-
connectionString: this.config.connectionString,
|
|
277
|
-
});
|
|
273
|
+
this.pool = new pgModule.Pool({ connectionString: this.config.connectionString });
|
|
278
274
|
this.ownsPool = true;
|
|
279
275
|
} else if (this.config.poolConfig) {
|
|
280
276
|
this.pool = new pgModule.Pool(this.config.poolConfig);
|
|
281
277
|
this.ownsPool = true;
|
|
282
278
|
} else {
|
|
283
279
|
throw new Error(
|
|
284
|
-
|
|
280
|
+
'PostgresStorageConfig must include either pool, connectionString, or poolConfig'
|
|
285
281
|
);
|
|
286
282
|
}
|
|
287
283
|
|
|
@@ -317,10 +313,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
317
313
|
*/
|
|
318
314
|
private async createTables(): Promise<void> {
|
|
319
315
|
// Create schema if not public
|
|
320
|
-
if (this.schema !==
|
|
321
|
-
await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(
|
|
322
|
-
this.db,
|
|
323
|
-
);
|
|
316
|
+
if (this.schema !== 'public') {
|
|
317
|
+
await sql`CREATE SCHEMA IF NOT EXISTS ${sql.ref(this.schema)}`.execute(this.db);
|
|
324
318
|
}
|
|
325
319
|
|
|
326
320
|
// Create runs table with new columns
|
|
@@ -352,11 +346,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
352
346
|
ADD COLUMN IF NOT EXISTS output_json JSONB,
|
|
353
347
|
ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0,
|
|
354
348
|
ADD COLUMN IF NOT EXISTS timeout_ms INTEGER
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// Ignore if columns already exist or syntax not supported
|
|
359
|
-
});
|
|
349
|
+
`.execute(this.db).catch(() => {
|
|
350
|
+
// Ignore if columns already exist or syntax not supported
|
|
351
|
+
});
|
|
360
352
|
|
|
361
353
|
// Create indexes for runs
|
|
362
354
|
await sql`
|
|
@@ -480,30 +472,25 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
480
472
|
* Create a new workflow run.
|
|
481
473
|
* Supports both legacy and new CreateRunInput interfaces.
|
|
482
474
|
*/
|
|
483
|
-
async createRun(
|
|
484
|
-
run: CreateRunInput | Omit<WorkflowRunRecord, "id" | "createdAt">,
|
|
485
|
-
): Promise<WorkflowRunRecord> {
|
|
475
|
+
async createRun(run: CreateRunInput | Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {
|
|
486
476
|
this.ensureInitialized();
|
|
487
|
-
const id =
|
|
477
|
+
const id = 'id' in run && run.id ? run.id : generateId();
|
|
488
478
|
const createdAt = new Date();
|
|
489
479
|
|
|
490
480
|
await this.qb
|
|
491
|
-
.insertInto(
|
|
481
|
+
.insertInto('runs')
|
|
492
482
|
.values({
|
|
493
483
|
id,
|
|
494
484
|
kind: run.kind,
|
|
495
485
|
status: run.status,
|
|
496
|
-
parent_run_id:
|
|
486
|
+
parent_run_id: 'parentRunId' in run ? (run.parentRunId ?? null) : null,
|
|
497
487
|
input_json: JSON.stringify(run.input),
|
|
498
488
|
metadata_json: JSON.stringify(run.metadata ?? {}),
|
|
499
489
|
context_json: JSON.stringify(run.context ?? {}), // Default to empty object
|
|
500
490
|
output_json: null,
|
|
501
|
-
error_json:
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
: null,
|
|
505
|
-
priority: "priority" in run ? (run.priority ?? 0) : 0,
|
|
506
|
-
timeout_ms: "timeoutMs" in run ? (run.timeoutMs ?? null) : null,
|
|
491
|
+
error_json: 'error' in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
|
|
492
|
+
priority: 'priority' in run ? (run.priority ?? 0) : 0,
|
|
493
|
+
timeout_ms: 'timeoutMs' in run ? (run.timeoutMs ?? null) : null,
|
|
507
494
|
created_at: createdAt,
|
|
508
495
|
started_at: null,
|
|
509
496
|
finished_at: null,
|
|
@@ -514,14 +501,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
514
501
|
id,
|
|
515
502
|
kind: run.kind,
|
|
516
503
|
status: run.status as RunStatus,
|
|
517
|
-
parentRunId:
|
|
504
|
+
parentRunId: 'parentRunId' in run ? run.parentRunId : undefined,
|
|
518
505
|
input: run.input,
|
|
519
506
|
context: run.context ?? {},
|
|
520
507
|
output: undefined,
|
|
521
508
|
error: undefined,
|
|
522
509
|
metadata: run.metadata ?? {},
|
|
523
|
-
priority:
|
|
524
|
-
timeoutMs:
|
|
510
|
+
priority: 'priority' in run ? (run.priority ?? 0) : 0,
|
|
511
|
+
timeoutMs: 'timeoutMs' in run ? run.timeoutMs : undefined,
|
|
525
512
|
createdAt,
|
|
526
513
|
};
|
|
527
514
|
}
|
|
@@ -529,9 +516,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
529
516
|
async getRun(runId: string): Promise<WorkflowRunRecord | null> {
|
|
530
517
|
this.ensureInitialized();
|
|
531
518
|
const row = await this.qb
|
|
532
|
-
.selectFrom(
|
|
519
|
+
.selectFrom('runs')
|
|
533
520
|
.selectAll()
|
|
534
|
-
.where(
|
|
521
|
+
.where('id', '=', runId)
|
|
535
522
|
.executeTakeFirst();
|
|
536
523
|
|
|
537
524
|
return row ? this.mapRunRow(row) : null;
|
|
@@ -541,10 +528,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
541
528
|
* Update a workflow run.
|
|
542
529
|
* Supports both legacy Partial<WorkflowRunRecord> and new UpdateRunInput interfaces.
|
|
543
530
|
*/
|
|
544
|
-
async updateRun(
|
|
545
|
-
runId: string,
|
|
546
|
-
updates: UpdateRunInput | Partial<WorkflowRunRecord>,
|
|
547
|
-
): Promise<void> {
|
|
531
|
+
async updateRun(runId: string, updates: UpdateRunInput | Partial<WorkflowRunRecord>): Promise<void> {
|
|
548
532
|
this.ensureInitialized();
|
|
549
533
|
const updateData: Partial<WorkflowRunsTable> = {};
|
|
550
534
|
|
|
@@ -554,13 +538,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
554
538
|
if (updates.context !== undefined) {
|
|
555
539
|
updateData.context_json = JSON.stringify(updates.context);
|
|
556
540
|
}
|
|
557
|
-
if (
|
|
541
|
+
if ('output' in updates && updates.output !== undefined) {
|
|
558
542
|
updateData.output_json = JSON.stringify(updates.output);
|
|
559
543
|
}
|
|
560
544
|
if (updates.error !== undefined) {
|
|
561
|
-
updateData.error_json = JSON.stringify(
|
|
562
|
-
sanitizeErrorForStorage(updates.error),
|
|
563
|
-
);
|
|
545
|
+
updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
|
|
564
546
|
}
|
|
565
547
|
if (updates.startedAt !== undefined) {
|
|
566
548
|
updateData.started_at = updates.startedAt;
|
|
@@ -571,9 +553,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
571
553
|
|
|
572
554
|
if (Object.keys(updateData).length > 0) {
|
|
573
555
|
await this.qb
|
|
574
|
-
.updateTable(
|
|
556
|
+
.updateTable('runs')
|
|
575
557
|
.set(updateData)
|
|
576
|
-
.where(
|
|
558
|
+
.where('id', '=', runId)
|
|
577
559
|
.execute();
|
|
578
560
|
}
|
|
579
561
|
}
|
|
@@ -587,54 +569,44 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
587
569
|
*/
|
|
588
570
|
private applyRunsFilters<T extends { where(col: any, op: any, val: any): T }>(
|
|
589
571
|
query: T,
|
|
590
|
-
options: ListRunsOptions
|
|
572
|
+
options: ListRunsOptions
|
|
591
573
|
): T {
|
|
592
574
|
if (options.kind) {
|
|
593
|
-
query = query.where(
|
|
575
|
+
query = query.where('kind', '=', options.kind);
|
|
594
576
|
}
|
|
595
577
|
if (options.status) {
|
|
596
|
-
const statuses = Array.isArray(options.status)
|
|
597
|
-
|
|
598
|
-
: [options.status];
|
|
599
|
-
query = query.where("status", "in", statuses);
|
|
578
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
579
|
+
query = query.where('status', 'in', statuses);
|
|
600
580
|
}
|
|
601
581
|
if (options.parentRunId !== undefined) {
|
|
602
|
-
query = query.where(
|
|
582
|
+
query = query.where('parent_run_id', '=', options.parentRunId);
|
|
603
583
|
}
|
|
604
584
|
return query;
|
|
605
585
|
}
|
|
606
586
|
|
|
607
|
-
async listRuns(
|
|
608
|
-
options: ListRunsOptions = {},
|
|
609
|
-
): Promise<PaginatedResult<WorkflowRunRecord>> {
|
|
587
|
+
async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {
|
|
610
588
|
this.ensureInitialized();
|
|
611
589
|
const limit = options.limit ?? 50;
|
|
612
590
|
const offset = options.offset ?? 0;
|
|
613
591
|
|
|
614
592
|
let query = this.applyRunsFilters(
|
|
615
|
-
this.qb.selectFrom(
|
|
616
|
-
options
|
|
593
|
+
this.qb.selectFrom('runs').selectAll(),
|
|
594
|
+
options
|
|
617
595
|
);
|
|
618
596
|
|
|
619
597
|
const countQuery = this.applyRunsFilters(
|
|
620
|
-
this.qb.selectFrom(
|
|
621
|
-
options
|
|
598
|
+
this.qb.selectFrom('runs').select(sql<number>`count(*)`.as('count')),
|
|
599
|
+
options
|
|
622
600
|
);
|
|
623
601
|
|
|
624
|
-
const countResult =
|
|
625
|
-
| { count?: string | number }
|
|
626
|
-
| undefined;
|
|
602
|
+
const countResult = await countQuery.executeTakeFirst() as { count?: string | number } | undefined;
|
|
627
603
|
const total = Number(countResult?.count ?? 0);
|
|
628
604
|
|
|
629
605
|
// Order
|
|
630
|
-
const orderBy = options.orderBy ??
|
|
631
|
-
const orderDirection = options.orderDirection ??
|
|
632
|
-
const orderColumn =
|
|
633
|
-
|
|
634
|
-
? "created_at"
|
|
635
|
-
: orderBy === "startedAt"
|
|
636
|
-
? "started_at"
|
|
637
|
-
: "finished_at";
|
|
606
|
+
const orderBy = options.orderBy ?? 'createdAt';
|
|
607
|
+
const orderDirection = options.orderDirection ?? 'desc';
|
|
608
|
+
const orderColumn = orderBy === 'createdAt' ? 'created_at' :
|
|
609
|
+
orderBy === 'startedAt' ? 'started_at' : 'finished_at';
|
|
638
610
|
|
|
639
611
|
query = query.orderBy(orderColumn, orderDirection);
|
|
640
612
|
|
|
@@ -644,7 +616,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
644
616
|
const rows = await query.execute();
|
|
645
617
|
|
|
646
618
|
return {
|
|
647
|
-
items: rows.map(
|
|
619
|
+
items: rows.map(row => this.mapRunRow(row)),
|
|
648
620
|
total,
|
|
649
621
|
};
|
|
650
622
|
}
|
|
@@ -653,14 +625,12 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
653
625
|
// Step Operations
|
|
654
626
|
// ============================================================================
|
|
655
627
|
|
|
656
|
-
async createStep(
|
|
657
|
-
step: Omit<WorkflowRunStepRecord, "id">,
|
|
658
|
-
): Promise<WorkflowRunStepRecord> {
|
|
628
|
+
async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {
|
|
659
629
|
this.ensureInitialized();
|
|
660
630
|
const id = generateId();
|
|
661
631
|
|
|
662
632
|
await this.qb
|
|
663
|
-
.insertInto(
|
|
633
|
+
.insertInto('workflow_run_steps')
|
|
664
634
|
.values({
|
|
665
635
|
id,
|
|
666
636
|
run_id: step.runId,
|
|
@@ -668,11 +638,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
668
638
|
step_name: step.stepName,
|
|
669
639
|
status: step.status,
|
|
670
640
|
attempt: step.attempt,
|
|
671
|
-
result_json:
|
|
672
|
-
|
|
673
|
-
error_json: step.error
|
|
674
|
-
? JSON.stringify(sanitizeErrorForStorage(step.error))
|
|
675
|
-
: null,
|
|
641
|
+
result_json: step.result !== undefined ? JSON.stringify(step.result) : null,
|
|
642
|
+
error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
|
|
676
643
|
started_at: step.startedAt ?? null,
|
|
677
644
|
finished_at: step.finishedAt ?? null,
|
|
678
645
|
})
|
|
@@ -684,18 +651,15 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
684
651
|
async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {
|
|
685
652
|
this.ensureInitialized();
|
|
686
653
|
const row = await this.qb
|
|
687
|
-
.selectFrom(
|
|
654
|
+
.selectFrom('workflow_run_steps')
|
|
688
655
|
.selectAll()
|
|
689
|
-
.where(
|
|
656
|
+
.where('id', '=', stepId)
|
|
690
657
|
.executeTakeFirst();
|
|
691
658
|
|
|
692
659
|
return row ? this.mapStepRow(row) : null;
|
|
693
660
|
}
|
|
694
661
|
|
|
695
|
-
async updateStep(
|
|
696
|
-
stepId: string,
|
|
697
|
-
updates: Partial<WorkflowRunStepRecord>,
|
|
698
|
-
): Promise<void> {
|
|
662
|
+
async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {
|
|
699
663
|
this.ensureInitialized();
|
|
700
664
|
const updateData: Partial<WorkflowRunStepsTable> = {};
|
|
701
665
|
|
|
@@ -709,9 +673,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
709
673
|
updateData.result_json = JSON.stringify(updates.result);
|
|
710
674
|
}
|
|
711
675
|
if (updates.error !== undefined) {
|
|
712
|
-
updateData.error_json = JSON.stringify(
|
|
713
|
-
sanitizeErrorForStorage(updates.error),
|
|
714
|
-
);
|
|
676
|
+
updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error));
|
|
715
677
|
}
|
|
716
678
|
if (updates.finishedAt !== undefined) {
|
|
717
679
|
updateData.finished_at = updates.finishedAt;
|
|
@@ -719,9 +681,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
719
681
|
|
|
720
682
|
if (Object.keys(updateData).length > 0) {
|
|
721
683
|
await this.qb
|
|
722
|
-
.updateTable(
|
|
684
|
+
.updateTable('workflow_run_steps')
|
|
723
685
|
.set(updateData)
|
|
724
|
-
.where(
|
|
686
|
+
.where('id', '=', stepId)
|
|
725
687
|
.execute();
|
|
726
688
|
}
|
|
727
689
|
}
|
|
@@ -729,64 +691,60 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
729
691
|
async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {
|
|
730
692
|
this.ensureInitialized();
|
|
731
693
|
const rows = await this.qb
|
|
732
|
-
.selectFrom(
|
|
694
|
+
.selectFrom('workflow_run_steps')
|
|
733
695
|
.selectAll()
|
|
734
|
-
.where(
|
|
735
|
-
.orderBy(
|
|
696
|
+
.where('run_id', '=', runId)
|
|
697
|
+
.orderBy('started_at', 'asc')
|
|
736
698
|
.execute();
|
|
737
699
|
|
|
738
|
-
return rows.map(
|
|
700
|
+
return rows.map(row => this.mapStepRow(row));
|
|
739
701
|
}
|
|
740
702
|
|
|
741
703
|
// ============================================================================
|
|
742
704
|
// Event Operations
|
|
743
705
|
// ============================================================================
|
|
744
706
|
|
|
745
|
-
async saveEvent(event: Omit<WorkflowEventRecord,
|
|
707
|
+
async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {
|
|
746
708
|
this.ensureInitialized();
|
|
747
709
|
const id = generateId();
|
|
748
710
|
|
|
749
711
|
await this.qb
|
|
750
|
-
.insertInto(
|
|
712
|
+
.insertInto('workflow_events')
|
|
751
713
|
.values({
|
|
752
714
|
id,
|
|
753
715
|
run_id: event.runId,
|
|
754
716
|
step_key: event.stepKey ?? null,
|
|
755
717
|
event_type: event.eventType,
|
|
756
718
|
level: event.level,
|
|
757
|
-
payload_json:
|
|
758
|
-
event.payload !== undefined ? JSON.stringify(event.payload) : null,
|
|
719
|
+
payload_json: event.payload !== undefined ? JSON.stringify(event.payload) : null,
|
|
759
720
|
timestamp: event.timestamp,
|
|
760
721
|
})
|
|
761
722
|
.execute();
|
|
762
723
|
}
|
|
763
724
|
|
|
764
|
-
async getEventsForRun(
|
|
765
|
-
runId: string,
|
|
766
|
-
options: ListEventsOptions = {},
|
|
767
|
-
): Promise<WorkflowEventRecord[]> {
|
|
725
|
+
async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {
|
|
768
726
|
this.ensureInitialized();
|
|
769
727
|
const limit = options.limit ?? 1000;
|
|
770
728
|
const offset = options.offset ?? 0;
|
|
771
729
|
|
|
772
730
|
let query = this.qb
|
|
773
|
-
.selectFrom(
|
|
731
|
+
.selectFrom('workflow_events')
|
|
774
732
|
.selectAll()
|
|
775
|
-
.where(
|
|
733
|
+
.where('run_id', '=', runId);
|
|
776
734
|
|
|
777
735
|
if (options.stepKey) {
|
|
778
|
-
query = query.where(
|
|
736
|
+
query = query.where('step_key', '=', options.stepKey);
|
|
779
737
|
}
|
|
780
738
|
|
|
781
739
|
if (options.level) {
|
|
782
|
-
query = query.where(
|
|
740
|
+
query = query.where('level', '=', options.level);
|
|
783
741
|
}
|
|
784
742
|
|
|
785
|
-
query = query.orderBy(
|
|
743
|
+
query = query.orderBy('timestamp', 'asc').limit(limit).offset(offset);
|
|
786
744
|
|
|
787
745
|
const rows = await query.execute();
|
|
788
746
|
|
|
789
|
-
return rows.map(
|
|
747
|
+
return rows.map(row => this.mapEventRow(row));
|
|
790
748
|
}
|
|
791
749
|
|
|
792
750
|
// ============================================================================
|
|
@@ -816,8 +774,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
816
774
|
async deleteOldRuns(olderThan: Date): Promise<number> {
|
|
817
775
|
this.ensureInitialized();
|
|
818
776
|
const result = await this.qb
|
|
819
|
-
.deleteFrom(
|
|
820
|
-
.where(
|
|
777
|
+
.deleteFrom('runs')
|
|
778
|
+
.where('created_at', '<', olderThan)
|
|
821
779
|
.executeTakeFirst();
|
|
822
780
|
|
|
823
781
|
return Number(result.numDeletedRows);
|
|
@@ -834,29 +792,27 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
834
792
|
async getInterruptedRuns(): Promise<WorkflowRunRecord[]> {
|
|
835
793
|
this.ensureInitialized();
|
|
836
794
|
const rows = await this.qb
|
|
837
|
-
.selectFrom(
|
|
795
|
+
.selectFrom('runs')
|
|
838
796
|
.selectAll()
|
|
839
|
-
.where(
|
|
840
|
-
.orderBy(
|
|
797
|
+
.where('status', 'in', ['queued', 'running'])
|
|
798
|
+
.orderBy('created_at', 'asc')
|
|
841
799
|
.execute();
|
|
842
800
|
|
|
843
|
-
return rows.map(
|
|
801
|
+
return rows.map(row => this.mapRunRow(row));
|
|
844
802
|
}
|
|
845
803
|
|
|
846
804
|
/**
|
|
847
805
|
* Get the last completed step for a run.
|
|
848
806
|
* Useful for resuming from a checkpoint.
|
|
849
807
|
*/
|
|
850
|
-
async getLastCompletedStep(
|
|
851
|
-
runId: string,
|
|
852
|
-
): Promise<WorkflowRunStepRecord | null> {
|
|
808
|
+
async getLastCompletedStep(runId: string): Promise<WorkflowRunStepRecord | null> {
|
|
853
809
|
this.ensureInitialized();
|
|
854
810
|
const row = await this.qb
|
|
855
|
-
.selectFrom(
|
|
811
|
+
.selectFrom('workflow_run_steps')
|
|
856
812
|
.selectAll()
|
|
857
|
-
.where(
|
|
858
|
-
.where(
|
|
859
|
-
.orderBy(
|
|
813
|
+
.where('run_id', '=', runId)
|
|
814
|
+
.where('status', '=', 'succeeded')
|
|
815
|
+
.orderBy('finished_at', 'desc')
|
|
860
816
|
.limit(1)
|
|
861
817
|
.executeTakeFirst();
|
|
862
818
|
|
|
@@ -874,15 +830,12 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
874
830
|
* @param workflowKinds - Optional list of workflow kinds to filter by
|
|
875
831
|
* @returns The dequeued run, or null if no runs are available
|
|
876
832
|
*/
|
|
877
|
-
async dequeueRun(
|
|
878
|
-
workflowKinds?: string[],
|
|
879
|
-
): Promise<WorkflowRunRecord | null> {
|
|
833
|
+
async dequeueRun(workflowKinds?: string[]): Promise<WorkflowRunRecord | null> {
|
|
880
834
|
this.ensureInitialized();
|
|
881
835
|
// Use raw SQL for FOR UPDATE SKIP LOCKED
|
|
882
|
-
const kindFilter =
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
: sql``;
|
|
836
|
+
const kindFilter = workflowKinds && workflowKinds.length > 0
|
|
837
|
+
? sql`AND kind = ANY(${workflowKinds}::text[])`
|
|
838
|
+
: sql``;
|
|
886
839
|
|
|
887
840
|
const result = await sql<WorkflowRunsTable>`
|
|
888
841
|
UPDATE ${sql.table(`${this.schema}.runs`)}
|
|
@@ -911,28 +864,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
911
864
|
kind: row.kind,
|
|
912
865
|
status: row.status as RunStatus,
|
|
913
866
|
parentRunId: row.parent_run_id ?? undefined,
|
|
914
|
-
input:
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
typeof row.context_json === "string"
|
|
920
|
-
? JSON.parse(row.context_json)
|
|
921
|
-
: row.context_json,
|
|
922
|
-
output: row.output_json
|
|
923
|
-
? typeof row.output_json === "string"
|
|
924
|
-
? JSON.parse(row.output_json)
|
|
925
|
-
: row.output_json
|
|
926
|
-
: undefined,
|
|
927
|
-
error: row.error_json
|
|
928
|
-
? typeof row.error_json === "string"
|
|
929
|
-
? JSON.parse(row.error_json)
|
|
930
|
-
: row.error_json
|
|
931
|
-
: undefined,
|
|
932
|
-
metadata:
|
|
933
|
-
typeof row.metadata_json === "string"
|
|
934
|
-
? JSON.parse(row.metadata_json)
|
|
935
|
-
: row.metadata_json,
|
|
867
|
+
input: typeof row.input_json === 'string' ? JSON.parse(row.input_json) : row.input_json,
|
|
868
|
+
context: typeof row.context_json === 'string' ? JSON.parse(row.context_json) : row.context_json,
|
|
869
|
+
output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
|
|
870
|
+
error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
|
|
871
|
+
metadata: typeof row.metadata_json === 'string' ? JSON.parse(row.metadata_json) : row.metadata_json,
|
|
936
872
|
priority: row.priority ?? 0,
|
|
937
873
|
timeoutMs: row.timeout_ms ?? undefined,
|
|
938
874
|
createdAt: new Date(row.created_at),
|
|
@@ -949,28 +885,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
949
885
|
id: row.id,
|
|
950
886
|
kind: row.kind,
|
|
951
887
|
status: row.status as ExtendedRunStatus,
|
|
952
|
-
input:
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
typeof row.metadata_json === "string"
|
|
958
|
-
? JSON.parse(row.metadata_json)
|
|
959
|
-
: row.metadata_json,
|
|
960
|
-
context:
|
|
961
|
-
typeof row.context_json === "string"
|
|
962
|
-
? JSON.parse(row.context_json)
|
|
963
|
-
: row.context_json,
|
|
964
|
-
output: row.output_json
|
|
965
|
-
? typeof row.output_json === "string"
|
|
966
|
-
? JSON.parse(row.output_json)
|
|
967
|
-
: row.output_json
|
|
968
|
-
: undefined,
|
|
969
|
-
error: row.error_json
|
|
970
|
-
? typeof row.error_json === "string"
|
|
971
|
-
? JSON.parse(row.error_json)
|
|
972
|
-
: row.error_json
|
|
973
|
-
: undefined,
|
|
888
|
+
input: typeof row.input_json === 'string' ? JSON.parse(row.input_json) : row.input_json,
|
|
889
|
+
metadata: typeof row.metadata_json === 'string' ? JSON.parse(row.metadata_json) : row.metadata_json,
|
|
890
|
+
context: typeof row.context_json === 'string' ? JSON.parse(row.context_json) : row.context_json,
|
|
891
|
+
output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
|
|
892
|
+
error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
|
|
974
893
|
priority: row.priority ?? 0,
|
|
975
894
|
timeoutMs: row.timeout_ms ?? undefined,
|
|
976
895
|
createdAt: new Date(row.created_at),
|
|
@@ -985,16 +904,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
985
904
|
runId: row.run_id,
|
|
986
905
|
stepName: row.step_name,
|
|
987
906
|
status: row.status as ExtendedStepStatus,
|
|
988
|
-
output: row.output_json
|
|
989
|
-
|
|
990
|
-
? JSON.parse(row.output_json)
|
|
991
|
-
: row.output_json
|
|
992
|
-
: undefined,
|
|
993
|
-
error: row.error_json
|
|
994
|
-
? typeof row.error_json === "string"
|
|
995
|
-
? JSON.parse(row.error_json)
|
|
996
|
-
: row.error_json
|
|
997
|
-
: undefined,
|
|
907
|
+
output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
|
|
908
|
+
error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
|
|
998
909
|
attempt: row.attempt,
|
|
999
910
|
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
1000
911
|
completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
|
|
@@ -1009,16 +920,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1009
920
|
stepName: row.step_name,
|
|
1010
921
|
status: row.status as StepStatus,
|
|
1011
922
|
attempt: row.attempt,
|
|
1012
|
-
result: row.result_json
|
|
1013
|
-
|
|
1014
|
-
? JSON.parse(row.result_json)
|
|
1015
|
-
: row.result_json
|
|
1016
|
-
: undefined,
|
|
1017
|
-
error: row.error_json
|
|
1018
|
-
? typeof row.error_json === "string"
|
|
1019
|
-
? JSON.parse(row.error_json)
|
|
1020
|
-
: row.error_json
|
|
1021
|
-
: undefined,
|
|
923
|
+
result: row.result_json ? (typeof row.result_json === 'string' ? JSON.parse(row.result_json) : row.result_json) : undefined,
|
|
924
|
+
error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
|
|
1022
925
|
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
1023
926
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
1024
927
|
};
|
|
@@ -1030,12 +933,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1030
933
|
runId: row.run_id,
|
|
1031
934
|
stepKey: row.step_key ?? undefined,
|
|
1032
935
|
eventType: row.event_type,
|
|
1033
|
-
level: row.level as
|
|
1034
|
-
payload: row.payload_json
|
|
1035
|
-
? typeof row.payload_json === "string"
|
|
1036
|
-
? JSON.parse(row.payload_json)
|
|
1037
|
-
: row.payload_json
|
|
1038
|
-
: undefined,
|
|
936
|
+
level: row.level as 'info' | 'warn' | 'error',
|
|
937
|
+
payload: row.payload_json ? (typeof row.payload_json === 'string' ? JSON.parse(row.payload_json) : row.payload_json) : undefined,
|
|
1039
938
|
timestamp: new Date(row.timestamp),
|
|
1040
939
|
};
|
|
1041
940
|
}
|
|
@@ -1050,7 +949,10 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1050
949
|
*/
|
|
1051
950
|
async deleteRun(id: string): Promise<void> {
|
|
1052
951
|
this.ensureInitialized();
|
|
1053
|
-
await this.qb
|
|
952
|
+
await this.qb
|
|
953
|
+
.deleteFrom('runs')
|
|
954
|
+
.where('id', '=', id)
|
|
955
|
+
.execute();
|
|
1054
956
|
}
|
|
1055
957
|
|
|
1056
958
|
/**
|
|
@@ -1112,16 +1014,13 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1112
1014
|
/**
|
|
1113
1015
|
* Get a specific step result by run ID and step name.
|
|
1114
1016
|
*/
|
|
1115
|
-
async getStepResult(
|
|
1116
|
-
runId: string,
|
|
1117
|
-
stepName: string,
|
|
1118
|
-
): Promise<StepResult | undefined> {
|
|
1017
|
+
async getStepResult(runId: string, stepName: string): Promise<StepResult | undefined> {
|
|
1119
1018
|
this.ensureInitialized();
|
|
1120
1019
|
const row = await this.qb
|
|
1121
|
-
.selectFrom(
|
|
1020
|
+
.selectFrom('stepflow_step_results')
|
|
1122
1021
|
.selectAll()
|
|
1123
|
-
.where(
|
|
1124
|
-
.where(
|
|
1022
|
+
.where('run_id', '=', runId)
|
|
1023
|
+
.where('step_name', '=', stepName)
|
|
1125
1024
|
.executeTakeFirst();
|
|
1126
1025
|
|
|
1127
1026
|
return row ? this.mapStepResultRow(row) : undefined;
|
|
@@ -1133,51 +1032,45 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1133
1032
|
async getStepResults(runId: string): Promise<StepResult[]> {
|
|
1134
1033
|
this.ensureInitialized();
|
|
1135
1034
|
const rows = await this.qb
|
|
1136
|
-
.selectFrom(
|
|
1035
|
+
.selectFrom('stepflow_step_results')
|
|
1137
1036
|
.selectAll()
|
|
1138
|
-
.where(
|
|
1139
|
-
.orderBy(
|
|
1037
|
+
.where('run_id', '=', runId)
|
|
1038
|
+
.orderBy('started_at', 'asc')
|
|
1140
1039
|
.execute();
|
|
1141
1040
|
|
|
1142
|
-
return rows.map(
|
|
1041
|
+
return rows.map(row => this.mapStepResultRow(row));
|
|
1143
1042
|
}
|
|
1144
1043
|
|
|
1145
1044
|
/**
|
|
1146
1045
|
* Save or update a step result.
|
|
1147
1046
|
* Uses upsert to handle both new and existing results.
|
|
1148
1047
|
*/
|
|
1149
|
-
async saveStepResult(
|
|
1150
|
-
result: Omit<StepResult, "id"> & { id?: string },
|
|
1151
|
-
): Promise<void> {
|
|
1048
|
+
async saveStepResult(result: Omit<StepResult, 'id'> & { id?: string }): Promise<void> {
|
|
1152
1049
|
this.ensureInitialized();
|
|
1153
1050
|
const id = result.id ?? generateId();
|
|
1154
1051
|
|
|
1155
1052
|
await this.qb
|
|
1156
|
-
.insertInto(
|
|
1053
|
+
.insertInto('stepflow_step_results')
|
|
1157
1054
|
.values({
|
|
1158
1055
|
id,
|
|
1159
1056
|
run_id: result.runId,
|
|
1160
1057
|
step_name: result.stepName,
|
|
1161
1058
|
status: result.status,
|
|
1162
1059
|
output_json: result.output ? JSON.stringify(result.output) : null,
|
|
1163
|
-
error_json: result.error
|
|
1164
|
-
? JSON.stringify(stripStack(result.error))
|
|
1165
|
-
: null,
|
|
1060
|
+
error_json: result.error ? JSON.stringify(stripStack(result.error)) : null,
|
|
1166
1061
|
attempt: result.attempt,
|
|
1167
1062
|
started_at: result.startedAt ?? null,
|
|
1168
1063
|
completed_at: result.completedAt ?? null,
|
|
1169
1064
|
})
|
|
1170
|
-
.onConflict(
|
|
1171
|
-
oc.columns([
|
|
1065
|
+
.onConflict(oc =>
|
|
1066
|
+
oc.columns(['run_id', 'step_name']).doUpdateSet({
|
|
1172
1067
|
status: result.status,
|
|
1173
1068
|
output_json: result.output ? JSON.stringify(result.output) : null,
|
|
1174
|
-
error_json: result.error
|
|
1175
|
-
? JSON.stringify(stripStack(result.error))
|
|
1176
|
-
: null,
|
|
1069
|
+
error_json: result.error ? JSON.stringify(stripStack(result.error)) : null,
|
|
1177
1070
|
attempt: result.attempt,
|
|
1178
1071
|
started_at: result.startedAt ?? null,
|
|
1179
1072
|
completed_at: result.completedAt ?? null,
|
|
1180
|
-
})
|
|
1073
|
+
})
|
|
1181
1074
|
)
|
|
1182
1075
|
.execute();
|
|
1183
1076
|
}
|
|
@@ -1191,20 +1084,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1191
1084
|
*/
|
|
1192
1085
|
async getStats(): Promise<{ runs: number; steps: number; events: number }> {
|
|
1193
1086
|
this.ensureInitialized();
|
|
1194
|
-
const [runsCount, stepsCount, eventsCount] =
|
|
1195
|
-
this.qb
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
this.qb
|
|
1200
|
-
.selectFrom("workflow_run_steps")
|
|
1201
|
-
.select(sql`count(*)`.as("count"))
|
|
1202
|
-
.executeTakeFirst(),
|
|
1203
|
-
this.qb
|
|
1204
|
-
.selectFrom("workflow_events")
|
|
1205
|
-
.select(sql`count(*)`.as("count"))
|
|
1206
|
-
.executeTakeFirst(),
|
|
1207
|
-
])) as { count?: string | number }[];
|
|
1087
|
+
const [runsCount, stepsCount, eventsCount] = await Promise.all([
|
|
1088
|
+
this.qb.selectFrom('runs').select(sql`count(*)`.as('count')).executeTakeFirst(),
|
|
1089
|
+
this.qb.selectFrom('workflow_run_steps').select(sql`count(*)`.as('count')).executeTakeFirst(),
|
|
1090
|
+
this.qb.selectFrom('workflow_events').select(sql`count(*)`.as('count')).executeTakeFirst(),
|
|
1091
|
+
]) as { count?: string | number }[];
|
|
1208
1092
|
|
|
1209
1093
|
return {
|
|
1210
1094
|
runs: Number(runsCount?.count ?? 0),
|
|
@@ -1223,38 +1107,33 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|
|
1223
1107
|
* This is used internally by PostgresStorageAdapter.transaction().
|
|
1224
1108
|
*/
|
|
1225
1109
|
class PostgresTransactionAdapter implements StorageAdapter {
|
|
1226
|
-
private qb: ReturnType<KyselyType<StepflowDatabase>[
|
|
1110
|
+
private qb: ReturnType<KyselyType<StepflowDatabase>['withSchema']>;
|
|
1227
1111
|
|
|
1228
1112
|
constructor(
|
|
1229
1113
|
private trx: KyselyType<StepflowDatabase>,
|
|
1230
|
-
private schema: string
|
|
1114
|
+
private schema: string
|
|
1231
1115
|
) {
|
|
1232
1116
|
this.qb = trx.withSchema(schema);
|
|
1233
1117
|
}
|
|
1234
1118
|
|
|
1235
|
-
async createRun(
|
|
1236
|
-
run
|
|
1237
|
-
): Promise<WorkflowRunRecord> {
|
|
1238
|
-
const id = "id" in run && run.id ? run.id : generateId();
|
|
1119
|
+
async createRun(run: CreateRunInput | Omit<WorkflowRunRecord, 'id' | 'createdAt'>): Promise<WorkflowRunRecord> {
|
|
1120
|
+
const id = 'id' in run && run.id ? run.id : generateId();
|
|
1239
1121
|
const createdAt = new Date();
|
|
1240
1122
|
|
|
1241
1123
|
await this.qb
|
|
1242
|
-
.insertInto(
|
|
1124
|
+
.insertInto('runs')
|
|
1243
1125
|
.values({
|
|
1244
1126
|
id,
|
|
1245
1127
|
kind: run.kind,
|
|
1246
1128
|
status: run.status,
|
|
1247
|
-
parent_run_id:
|
|
1129
|
+
parent_run_id: 'parentRunId' in run ? (run.parentRunId ?? null) : null,
|
|
1248
1130
|
input_json: JSON.stringify(run.input),
|
|
1249
1131
|
metadata_json: JSON.stringify(run.metadata ?? {}),
|
|
1250
1132
|
context_json: JSON.stringify(run.context ?? {}),
|
|
1251
1133
|
output_json: null,
|
|
1252
|
-
error_json:
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
: null,
|
|
1256
|
-
priority: "priority" in run ? (run.priority ?? 0) : 0,
|
|
1257
|
-
timeout_ms: "timeoutMs" in run ? (run.timeoutMs ?? null) : null,
|
|
1134
|
+
error_json: 'error' in run && run.error ? JSON.stringify(sanitizeErrorForStorage(run.error)) : null,
|
|
1135
|
+
priority: 'priority' in run ? (run.priority ?? 0) : 0,
|
|
1136
|
+
timeout_ms: 'timeoutMs' in run ? (run.timeoutMs ?? null) : null,
|
|
1258
1137
|
created_at: createdAt,
|
|
1259
1138
|
started_at: null,
|
|
1260
1139
|
finished_at: null,
|
|
@@ -1265,104 +1144,77 @@ class PostgresTransactionAdapter implements StorageAdapter {
|
|
|
1265
1144
|
id,
|
|
1266
1145
|
kind: run.kind,
|
|
1267
1146
|
status: run.status as RunStatus,
|
|
1268
|
-
parentRunId:
|
|
1147
|
+
parentRunId: 'parentRunId' in run ? run.parentRunId : undefined,
|
|
1269
1148
|
input: run.input,
|
|
1270
1149
|
context: run.context ?? {},
|
|
1271
1150
|
output: undefined,
|
|
1272
1151
|
error: undefined,
|
|
1273
1152
|
metadata: run.metadata ?? {},
|
|
1274
|
-
priority:
|
|
1275
|
-
timeoutMs:
|
|
1153
|
+
priority: 'priority' in run ? (run.priority ?? 0) : 0,
|
|
1154
|
+
timeoutMs: 'timeoutMs' in run ? run.timeoutMs : undefined,
|
|
1276
1155
|
createdAt,
|
|
1277
1156
|
};
|
|
1278
1157
|
}
|
|
1279
1158
|
|
|
1280
1159
|
async getRun(runId: string): Promise<WorkflowRunRecord | null> {
|
|
1281
1160
|
const row = await this.qb
|
|
1282
|
-
.selectFrom(
|
|
1161
|
+
.selectFrom('runs')
|
|
1283
1162
|
.selectAll()
|
|
1284
|
-
.where(
|
|
1163
|
+
.where('id', '=', runId)
|
|
1285
1164
|
.executeTakeFirst();
|
|
1286
1165
|
|
|
1287
1166
|
return row ? this.mapRunRow(row) : null;
|
|
1288
1167
|
}
|
|
1289
1168
|
|
|
1290
|
-
async updateRun(
|
|
1291
|
-
runId: string,
|
|
1292
|
-
updates: UpdateRunInput | Partial<WorkflowRunRecord>,
|
|
1293
|
-
): Promise<void> {
|
|
1169
|
+
async updateRun(runId: string, updates: UpdateRunInput | Partial<WorkflowRunRecord>): Promise<void> {
|
|
1294
1170
|
const updateData: Partial<WorkflowRunsTable> = {};
|
|
1295
1171
|
|
|
1296
1172
|
if (updates.status !== undefined) updateData.status = updates.status;
|
|
1297
|
-
if (updates.context !== undefined)
|
|
1298
|
-
|
|
1299
|
-
if (
|
|
1300
|
-
|
|
1301
|
-
if (updates.
|
|
1302
|
-
updateData.error_json = JSON.stringify(
|
|
1303
|
-
sanitizeErrorForStorage(updates.error as WorkflowError),
|
|
1304
|
-
);
|
|
1305
|
-
if (updates.startedAt !== undefined)
|
|
1306
|
-
updateData.started_at = updates.startedAt;
|
|
1307
|
-
if (updates.finishedAt !== undefined)
|
|
1308
|
-
updateData.finished_at = updates.finishedAt;
|
|
1173
|
+
if (updates.context !== undefined) updateData.context_json = JSON.stringify(updates.context);
|
|
1174
|
+
if ('output' in updates && updates.output !== undefined) updateData.output_json = JSON.stringify(updates.output);
|
|
1175
|
+
if (updates.error !== undefined) updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error as WorkflowError));
|
|
1176
|
+
if (updates.startedAt !== undefined) updateData.started_at = updates.startedAt;
|
|
1177
|
+
if (updates.finishedAt !== undefined) updateData.finished_at = updates.finishedAt;
|
|
1309
1178
|
|
|
1310
1179
|
if (Object.keys(updateData).length > 0) {
|
|
1311
|
-
await this.qb
|
|
1312
|
-
.updateTable("runs")
|
|
1313
|
-
.set(updateData)
|
|
1314
|
-
.where("id", "=", runId)
|
|
1315
|
-
.execute();
|
|
1180
|
+
await this.qb.updateTable('runs').set(updateData).where('id', '=', runId).execute();
|
|
1316
1181
|
}
|
|
1317
1182
|
}
|
|
1318
1183
|
|
|
1319
|
-
async listRuns(
|
|
1320
|
-
options: ListRunsOptions = {},
|
|
1321
|
-
): Promise<PaginatedResult<WorkflowRunRecord>> {
|
|
1184
|
+
async listRuns(options: ListRunsOptions = {}): Promise<PaginatedResult<WorkflowRunRecord>> {
|
|
1322
1185
|
const limit = options.limit ?? 50;
|
|
1323
1186
|
const offset = options.offset ?? 0;
|
|
1324
1187
|
|
|
1325
|
-
let query = this.qb.selectFrom(
|
|
1188
|
+
let query = this.qb.selectFrom('runs').selectAll();
|
|
1326
1189
|
|
|
1327
|
-
if (options.kind) query = query.where(
|
|
1190
|
+
if (options.kind) query = query.where('kind', '=', options.kind);
|
|
1328
1191
|
if (options.status) {
|
|
1329
|
-
const statuses = Array.isArray(options.status)
|
|
1330
|
-
|
|
1331
|
-
: [options.status];
|
|
1332
|
-
query = query.where("status", "in", statuses);
|
|
1192
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
1193
|
+
query = query.where('status', 'in', statuses);
|
|
1333
1194
|
}
|
|
1334
1195
|
if (options.parentRunId !== undefined) {
|
|
1335
|
-
query = query.where(
|
|
1196
|
+
query = query.where('parent_run_id', '=', options.parentRunId);
|
|
1336
1197
|
}
|
|
1337
1198
|
|
|
1338
|
-
const orderColumn =
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
: options.orderBy === "startedAt"
|
|
1342
|
-
? "started_at"
|
|
1343
|
-
: "finished_at";
|
|
1344
|
-
const orderDirection = options.orderDirection ?? "desc";
|
|
1199
|
+
const orderColumn = (options.orderBy ?? 'createdAt') === 'createdAt' ? 'created_at' :
|
|
1200
|
+
options.orderBy === 'startedAt' ? 'started_at' : 'finished_at';
|
|
1201
|
+
const orderDirection = options.orderDirection ?? 'desc';
|
|
1345
1202
|
|
|
1346
|
-
query = query
|
|
1347
|
-
.orderBy(orderColumn, orderDirection)
|
|
1348
|
-
.limit(limit)
|
|
1349
|
-
.offset(offset);
|
|
1203
|
+
query = query.orderBy(orderColumn, orderDirection).limit(limit).offset(offset);
|
|
1350
1204
|
|
|
1351
1205
|
const rows = await query.execute();
|
|
1352
1206
|
|
|
1353
1207
|
return {
|
|
1354
|
-
items: rows.map(
|
|
1208
|
+
items: rows.map(row => this.mapRunRow(row)),
|
|
1355
1209
|
total: rows.length, // Simplified for transaction context
|
|
1356
1210
|
};
|
|
1357
1211
|
}
|
|
1358
1212
|
|
|
1359
|
-
async createStep(
|
|
1360
|
-
step: Omit<WorkflowRunStepRecord, "id">,
|
|
1361
|
-
): Promise<WorkflowRunStepRecord> {
|
|
1213
|
+
async createStep(step: Omit<WorkflowRunStepRecord, 'id'>): Promise<WorkflowRunStepRecord> {
|
|
1362
1214
|
const id = generateId();
|
|
1363
1215
|
|
|
1364
1216
|
await this.qb
|
|
1365
|
-
.insertInto(
|
|
1217
|
+
.insertInto('workflow_run_steps')
|
|
1366
1218
|
.values({
|
|
1367
1219
|
id,
|
|
1368
1220
|
run_id: step.runId,
|
|
@@ -1370,11 +1222,8 @@ class PostgresTransactionAdapter implements StorageAdapter {
|
|
|
1370
1222
|
step_name: step.stepName,
|
|
1371
1223
|
status: step.status,
|
|
1372
1224
|
attempt: step.attempt,
|
|
1373
|
-
result_json:
|
|
1374
|
-
|
|
1375
|
-
error_json: step.error
|
|
1376
|
-
? JSON.stringify(sanitizeErrorForStorage(step.error))
|
|
1377
|
-
: null,
|
|
1225
|
+
result_json: step.result !== undefined ? JSON.stringify(step.result) : null,
|
|
1226
|
+
error_json: step.error ? JSON.stringify(sanitizeErrorForStorage(step.error)) : null,
|
|
1378
1227
|
started_at: step.startedAt ?? null,
|
|
1379
1228
|
finished_at: step.finishedAt ?? null,
|
|
1380
1229
|
})
|
|
@@ -1385,88 +1234,72 @@ class PostgresTransactionAdapter implements StorageAdapter {
|
|
|
1385
1234
|
|
|
1386
1235
|
async getStep(stepId: string): Promise<WorkflowRunStepRecord | null> {
|
|
1387
1236
|
const row = await this.qb
|
|
1388
|
-
.selectFrom(
|
|
1237
|
+
.selectFrom('workflow_run_steps')
|
|
1389
1238
|
.selectAll()
|
|
1390
|
-
.where(
|
|
1239
|
+
.where('id', '=', stepId)
|
|
1391
1240
|
.executeTakeFirst();
|
|
1392
1241
|
|
|
1393
1242
|
return row ? this.mapStepRow(row) : null;
|
|
1394
1243
|
}
|
|
1395
1244
|
|
|
1396
|
-
async updateStep(
|
|
1397
|
-
stepId: string,
|
|
1398
|
-
updates: Partial<WorkflowRunStepRecord>,
|
|
1399
|
-
): Promise<void> {
|
|
1245
|
+
async updateStep(stepId: string, updates: Partial<WorkflowRunStepRecord>): Promise<void> {
|
|
1400
1246
|
const updateData: Partial<WorkflowRunStepsTable> = {};
|
|
1401
1247
|
|
|
1402
1248
|
if (updates.status !== undefined) updateData.status = updates.status;
|
|
1403
1249
|
if (updates.attempt !== undefined) updateData.attempt = updates.attempt;
|
|
1404
|
-
if (updates.result !== undefined)
|
|
1405
|
-
|
|
1406
|
-
if (updates.
|
|
1407
|
-
updateData.error_json = JSON.stringify(
|
|
1408
|
-
sanitizeErrorForStorage(updates.error as WorkflowError),
|
|
1409
|
-
);
|
|
1410
|
-
if (updates.finishedAt !== undefined)
|
|
1411
|
-
updateData.finished_at = updates.finishedAt;
|
|
1250
|
+
if (updates.result !== undefined) updateData.result_json = JSON.stringify(updates.result);
|
|
1251
|
+
if (updates.error !== undefined) updateData.error_json = JSON.stringify(sanitizeErrorForStorage(updates.error as WorkflowError));
|
|
1252
|
+
if (updates.finishedAt !== undefined) updateData.finished_at = updates.finishedAt;
|
|
1412
1253
|
|
|
1413
1254
|
if (Object.keys(updateData).length > 0) {
|
|
1414
|
-
await this.qb
|
|
1415
|
-
.updateTable("workflow_run_steps")
|
|
1416
|
-
.set(updateData)
|
|
1417
|
-
.where("id", "=", stepId)
|
|
1418
|
-
.execute();
|
|
1255
|
+
await this.qb.updateTable('workflow_run_steps').set(updateData).where('id', '=', stepId).execute();
|
|
1419
1256
|
}
|
|
1420
1257
|
}
|
|
1421
1258
|
|
|
1422
1259
|
async getStepsForRun(runId: string): Promise<WorkflowRunStepRecord[]> {
|
|
1423
1260
|
const rows = await this.qb
|
|
1424
|
-
.selectFrom(
|
|
1261
|
+
.selectFrom('workflow_run_steps')
|
|
1425
1262
|
.selectAll()
|
|
1426
|
-
.where(
|
|
1427
|
-
.orderBy(
|
|
1263
|
+
.where('run_id', '=', runId)
|
|
1264
|
+
.orderBy('started_at', 'asc')
|
|
1428
1265
|
.execute();
|
|
1429
1266
|
|
|
1430
|
-
return rows.map(
|
|
1267
|
+
return rows.map(row => this.mapStepRow(row));
|
|
1431
1268
|
}
|
|
1432
1269
|
|
|
1433
|
-
async saveEvent(event: Omit<WorkflowEventRecord,
|
|
1270
|
+
async saveEvent(event: Omit<WorkflowEventRecord, 'id'>): Promise<void> {
|
|
1434
1271
|
const id = generateId();
|
|
1435
1272
|
|
|
1436
1273
|
await this.qb
|
|
1437
|
-
.insertInto(
|
|
1274
|
+
.insertInto('workflow_events')
|
|
1438
1275
|
.values({
|
|
1439
1276
|
id,
|
|
1440
1277
|
run_id: event.runId,
|
|
1441
1278
|
step_key: event.stepKey ?? null,
|
|
1442
1279
|
event_type: event.eventType,
|
|
1443
1280
|
level: event.level,
|
|
1444
|
-
payload_json:
|
|
1445
|
-
event.payload !== undefined ? JSON.stringify(event.payload) : null,
|
|
1281
|
+
payload_json: event.payload !== undefined ? JSON.stringify(event.payload) : null,
|
|
1446
1282
|
timestamp: event.timestamp,
|
|
1447
1283
|
})
|
|
1448
1284
|
.execute();
|
|
1449
1285
|
}
|
|
1450
1286
|
|
|
1451
|
-
async getEventsForRun(
|
|
1452
|
-
runId: string,
|
|
1453
|
-
options: ListEventsOptions = {},
|
|
1454
|
-
): Promise<WorkflowEventRecord[]> {
|
|
1287
|
+
async getEventsForRun(runId: string, options: ListEventsOptions = {}): Promise<WorkflowEventRecord[]> {
|
|
1455
1288
|
let query = this.qb
|
|
1456
|
-
.selectFrom(
|
|
1289
|
+
.selectFrom('workflow_events')
|
|
1457
1290
|
.selectAll()
|
|
1458
|
-
.where(
|
|
1291
|
+
.where('run_id', '=', runId);
|
|
1459
1292
|
|
|
1460
|
-
if (options.stepKey) query = query.where(
|
|
1461
|
-
if (options.level) query = query.where(
|
|
1293
|
+
if (options.stepKey) query = query.where('step_key', '=', options.stepKey);
|
|
1294
|
+
if (options.level) query = query.where('level', '=', options.level);
|
|
1462
1295
|
|
|
1463
1296
|
const rows = await query
|
|
1464
|
-
.orderBy(
|
|
1297
|
+
.orderBy('timestamp', 'asc')
|
|
1465
1298
|
.limit(options.limit ?? 1000)
|
|
1466
1299
|
.offset(options.offset ?? 0)
|
|
1467
1300
|
.execute();
|
|
1468
1301
|
|
|
1469
|
-
return rows.map(
|
|
1302
|
+
return rows.map(row => this.mapEventRow(row));
|
|
1470
1303
|
}
|
|
1471
1304
|
|
|
1472
1305
|
private mapRunRow(row: WorkflowRunsTable): WorkflowRunRecord {
|
|
@@ -1475,28 +1308,11 @@ class PostgresTransactionAdapter implements StorageAdapter {
|
|
|
1475
1308
|
kind: row.kind,
|
|
1476
1309
|
status: row.status as RunStatus,
|
|
1477
1310
|
parentRunId: row.parent_run_id ?? undefined,
|
|
1478
|
-
input:
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
typeof row.context_json === "string"
|
|
1484
|
-
? JSON.parse(row.context_json)
|
|
1485
|
-
: row.context_json,
|
|
1486
|
-
output: row.output_json
|
|
1487
|
-
? typeof row.output_json === "string"
|
|
1488
|
-
? JSON.parse(row.output_json)
|
|
1489
|
-
: row.output_json
|
|
1490
|
-
: undefined,
|
|
1491
|
-
error: row.error_json
|
|
1492
|
-
? typeof row.error_json === "string"
|
|
1493
|
-
? JSON.parse(row.error_json)
|
|
1494
|
-
: row.error_json
|
|
1495
|
-
: undefined,
|
|
1496
|
-
metadata:
|
|
1497
|
-
typeof row.metadata_json === "string"
|
|
1498
|
-
? JSON.parse(row.metadata_json)
|
|
1499
|
-
: row.metadata_json,
|
|
1311
|
+
input: typeof row.input_json === 'string' ? JSON.parse(row.input_json) : row.input_json,
|
|
1312
|
+
context: typeof row.context_json === 'string' ? JSON.parse(row.context_json) : row.context_json,
|
|
1313
|
+
output: row.output_json ? (typeof row.output_json === 'string' ? JSON.parse(row.output_json) : row.output_json) : undefined,
|
|
1314
|
+
error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
|
|
1315
|
+
metadata: typeof row.metadata_json === 'string' ? JSON.parse(row.metadata_json) : row.metadata_json,
|
|
1500
1316
|
priority: row.priority ?? 0,
|
|
1501
1317
|
timeoutMs: row.timeout_ms ?? undefined,
|
|
1502
1318
|
createdAt: new Date(row.created_at),
|
|
@@ -1513,16 +1329,8 @@ class PostgresTransactionAdapter implements StorageAdapter {
|
|
|
1513
1329
|
stepName: row.step_name,
|
|
1514
1330
|
status: row.status as StepStatus,
|
|
1515
1331
|
attempt: row.attempt,
|
|
1516
|
-
result: row.result_json
|
|
1517
|
-
|
|
1518
|
-
? JSON.parse(row.result_json)
|
|
1519
|
-
: row.result_json
|
|
1520
|
-
: undefined,
|
|
1521
|
-
error: row.error_json
|
|
1522
|
-
? typeof row.error_json === "string"
|
|
1523
|
-
? JSON.parse(row.error_json)
|
|
1524
|
-
: row.error_json
|
|
1525
|
-
: undefined,
|
|
1332
|
+
result: row.result_json ? (typeof row.result_json === 'string' ? JSON.parse(row.result_json) : row.result_json) : undefined,
|
|
1333
|
+
error: row.error_json ? (typeof row.error_json === 'string' ? JSON.parse(row.error_json) : row.error_json) : undefined,
|
|
1526
1334
|
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
1527
1335
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
1528
1336
|
};
|
|
@@ -1534,12 +1342,8 @@ class PostgresTransactionAdapter implements StorageAdapter {
|
|
|
1534
1342
|
runId: row.run_id,
|
|
1535
1343
|
stepKey: row.step_key ?? undefined,
|
|
1536
1344
|
eventType: row.event_type,
|
|
1537
|
-
level: row.level as
|
|
1538
|
-
payload: row.payload_json
|
|
1539
|
-
? typeof row.payload_json === "string"
|
|
1540
|
-
? JSON.parse(row.payload_json)
|
|
1541
|
-
: row.payload_json
|
|
1542
|
-
: undefined,
|
|
1345
|
+
level: row.level as 'info' | 'warn' | 'error',
|
|
1346
|
+
payload: row.payload_json ? (typeof row.payload_json === 'string' ? JSON.parse(row.payload_json) : row.payload_json) : undefined,
|
|
1543
1347
|
timestamp: new Date(row.timestamp),
|
|
1544
1348
|
};
|
|
1545
1349
|
}
|