@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/core/engine.ts
CHANGED
|
@@ -12,24 +12,19 @@ import type {
|
|
|
12
12
|
WorkflowContext,
|
|
13
13
|
Logger,
|
|
14
14
|
SpawnChildOptions,
|
|
15
|
-
} from
|
|
16
|
-
import type { StorageAdapter, WorkflowRunRecord } from
|
|
17
|
-
import type {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
WorkflowEvent,
|
|
22
|
-
} from "../events/types";
|
|
23
|
-
import { MemoryStorageAdapter } from "../storage/memory";
|
|
24
|
-
import { MemoryEventTransport } from "../events/memory";
|
|
25
|
-
import { executeWorkflow } from "./orchestrator";
|
|
15
|
+
} from './types';
|
|
16
|
+
import type { StorageAdapter, WorkflowRunRecord } from '../storage/types';
|
|
17
|
+
import type { EventTransport, EventCallback, Unsubscribe, WorkflowEvent } from '../events/types';
|
|
18
|
+
import { MemoryStorageAdapter } from '../storage/memory';
|
|
19
|
+
import { MemoryEventTransport } from '../events/memory';
|
|
20
|
+
import { executeWorkflow } from './orchestrator';
|
|
26
21
|
import {
|
|
27
22
|
WorkflowNotFoundError,
|
|
28
23
|
WorkflowAlreadyRegisteredError,
|
|
29
24
|
RunNotFoundError,
|
|
30
25
|
WaitForRunTimeoutError,
|
|
31
|
-
} from
|
|
32
|
-
import { ConsoleLogger } from
|
|
26
|
+
} from '../utils/errors';
|
|
27
|
+
import { ConsoleLogger } from '../utils/logger';
|
|
33
28
|
|
|
34
29
|
// ============================================================================
|
|
35
30
|
// Configuration Types
|
|
@@ -129,7 +124,7 @@ export class WorkflowEngine {
|
|
|
129
124
|
private events: EventTransport;
|
|
130
125
|
private logger: Logger;
|
|
131
126
|
private activeRuns = new Map<string, AbortController>();
|
|
132
|
-
private settings: NonNullable<WorkflowEngineConfig[
|
|
127
|
+
private settings: NonNullable<WorkflowEngineConfig['settings']>;
|
|
133
128
|
private runQueue: QueuedRun[] = [];
|
|
134
129
|
|
|
135
130
|
constructor(config: WorkflowEngineConfig = {}) {
|
|
@@ -187,7 +182,7 @@ export class WorkflowEngine {
|
|
|
187
182
|
* @throws WorkflowAlreadyRegisteredError if already registered
|
|
188
183
|
*/
|
|
189
184
|
registerWorkflow<TInput = Record<string, unknown>>(
|
|
190
|
-
definition: WorkflowDefinition<TInput
|
|
185
|
+
definition: WorkflowDefinition<TInput>
|
|
191
186
|
): void {
|
|
192
187
|
if (this.registry.has(definition.kind)) {
|
|
193
188
|
throw new WorkflowAlreadyRegisteredError(definition.kind);
|
|
@@ -241,7 +236,7 @@ export class WorkflowEngine {
|
|
|
241
236
|
* @throws WorkflowNotFoundError if the workflow kind is not registered
|
|
242
237
|
*/
|
|
243
238
|
async startRun<TInput = Record<string, unknown>>(
|
|
244
|
-
options: StartRunOptions<TInput
|
|
239
|
+
options: StartRunOptions<TInput>
|
|
245
240
|
): Promise<string> {
|
|
246
241
|
const definition = this.registry.get(options.kind);
|
|
247
242
|
if (!definition) {
|
|
@@ -251,7 +246,7 @@ export class WorkflowEngine {
|
|
|
251
246
|
// Create run record (status: queued)
|
|
252
247
|
const run = await this.storage.createRun({
|
|
253
248
|
kind: options.kind,
|
|
254
|
-
status:
|
|
249
|
+
status: 'queued',
|
|
255
250
|
parentRunId: options.parentRunId,
|
|
256
251
|
input: (options.input ?? {}) as Record<string, unknown>,
|
|
257
252
|
metadata: options.metadata ?? {},
|
|
@@ -264,13 +259,9 @@ export class WorkflowEngine {
|
|
|
264
259
|
this.events.emit({
|
|
265
260
|
runId,
|
|
266
261
|
kind: options.kind,
|
|
267
|
-
eventType:
|
|
262
|
+
eventType: 'run.created',
|
|
268
263
|
timestamp: new Date(),
|
|
269
|
-
payload: {
|
|
270
|
-
input: options.input,
|
|
271
|
-
metadata: options.metadata,
|
|
272
|
-
priority: options.priority ?? 0,
|
|
273
|
-
},
|
|
264
|
+
payload: { input: options.input, metadata: options.metadata, priority: options.priority ?? 0 },
|
|
274
265
|
});
|
|
275
266
|
|
|
276
267
|
// Check if we have capacity to start immediately
|
|
@@ -281,7 +272,7 @@ export class WorkflowEngine {
|
|
|
281
272
|
definition,
|
|
282
273
|
(options.input ?? {}) as Record<string, unknown>,
|
|
283
274
|
options.metadata,
|
|
284
|
-
options.delay
|
|
275
|
+
options.delay
|
|
285
276
|
);
|
|
286
277
|
} else {
|
|
287
278
|
// Queue the run for later execution
|
|
@@ -294,15 +285,13 @@ export class WorkflowEngine {
|
|
|
294
285
|
queuedAt: new Date(),
|
|
295
286
|
});
|
|
296
287
|
|
|
297
|
-
this.logger.debug(
|
|
298
|
-
`Run ${runId} queued (${this.runQueue.length} in queue)`,
|
|
299
|
-
);
|
|
288
|
+
this.logger.debug(`Run ${runId} queued (${this.runQueue.length} in queue)`);
|
|
300
289
|
|
|
301
290
|
// Emit queued event
|
|
302
291
|
this.events.emit({
|
|
303
292
|
runId,
|
|
304
293
|
kind: options.kind,
|
|
305
|
-
eventType:
|
|
294
|
+
eventType: 'run.queued',
|
|
306
295
|
timestamp: new Date(),
|
|
307
296
|
payload: { queuePosition: this.runQueue.length },
|
|
308
297
|
});
|
|
@@ -337,7 +326,7 @@ export class WorkflowEngine {
|
|
|
337
326
|
definition: WorkflowDefinition,
|
|
338
327
|
input: Record<string, unknown>,
|
|
339
328
|
metadata?: Record<string, unknown>,
|
|
340
|
-
delay?: number
|
|
329
|
+
delay?: number
|
|
341
330
|
): void {
|
|
342
331
|
this.launchRun(runId, definition, input, metadata, delay);
|
|
343
332
|
}
|
|
@@ -352,10 +341,7 @@ export class WorkflowEngine {
|
|
|
352
341
|
input: Record<string, unknown>,
|
|
353
342
|
metadata?: Record<string, unknown>,
|
|
354
343
|
delay?: number,
|
|
355
|
-
checkpoint?: {
|
|
356
|
-
completedStepKeys: Set<string>;
|
|
357
|
-
results: Record<string, unknown>;
|
|
358
|
-
},
|
|
344
|
+
checkpoint?: { completedStepKeys: Set<string>; results: Record<string, unknown> }
|
|
359
345
|
): void {
|
|
360
346
|
const abortController = new AbortController();
|
|
361
347
|
this.activeRuns.set(runId, abortController);
|
|
@@ -371,8 +357,7 @@ export class WorkflowEngine {
|
|
|
371
357
|
events: this.events,
|
|
372
358
|
logger: this.logger,
|
|
373
359
|
abortController,
|
|
374
|
-
spawnChild: (childOptions: SpawnChildOptions) =>
|
|
375
|
-
this.spawnChild(runId, childOptions),
|
|
360
|
+
spawnChild: (childOptions: SpawnChildOptions) => this.spawnChild(runId, childOptions),
|
|
376
361
|
checkpoint,
|
|
377
362
|
});
|
|
378
363
|
} finally {
|
|
@@ -400,11 +385,16 @@ export class WorkflowEngine {
|
|
|
400
385
|
this.events.emit({
|
|
401
386
|
runId: next.runId,
|
|
402
387
|
kind: next.definition.kind,
|
|
403
|
-
eventType:
|
|
388
|
+
eventType: 'run.dequeued',
|
|
404
389
|
timestamp: new Date(),
|
|
405
390
|
});
|
|
406
391
|
|
|
407
|
-
this.executeRun(
|
|
392
|
+
this.executeRun(
|
|
393
|
+
next.runId,
|
|
394
|
+
next.definition,
|
|
395
|
+
next.input,
|
|
396
|
+
next.metadata
|
|
397
|
+
);
|
|
408
398
|
}
|
|
409
399
|
}
|
|
410
400
|
|
|
@@ -412,10 +402,7 @@ export class WorkflowEngine {
|
|
|
412
402
|
* Start a child workflow from within a parent workflow.
|
|
413
403
|
* Called internally by the context.spawnChild helper.
|
|
414
404
|
*/
|
|
415
|
-
private async spawnChild(
|
|
416
|
-
parentRunId: string,
|
|
417
|
-
options: SpawnChildOptions,
|
|
418
|
-
): Promise<string> {
|
|
405
|
+
private async spawnChild(parentRunId: string, options: SpawnChildOptions): Promise<string> {
|
|
419
406
|
return this.startRun({
|
|
420
407
|
kind: options.kind,
|
|
421
408
|
input: options.input,
|
|
@@ -438,12 +425,12 @@ export class WorkflowEngine {
|
|
|
438
425
|
}
|
|
439
426
|
|
|
440
427
|
// Don't overwrite terminal statuses
|
|
441
|
-
if ([
|
|
428
|
+
if (['succeeded', 'failed', 'canceled', 'timeout'].includes(run.status)) {
|
|
442
429
|
return;
|
|
443
430
|
}
|
|
444
431
|
|
|
445
432
|
// Remove from queue if queued
|
|
446
|
-
const queueIndex = this.runQueue.findIndex(
|
|
433
|
+
const queueIndex = this.runQueue.findIndex(q => q.runId === runId);
|
|
447
434
|
if (queueIndex !== -1) {
|
|
448
435
|
this.runQueue.splice(queueIndex, 1);
|
|
449
436
|
}
|
|
@@ -456,14 +443,14 @@ export class WorkflowEngine {
|
|
|
456
443
|
|
|
457
444
|
// Update status in storage (the orchestrator will also update on completion)
|
|
458
445
|
await this.storage.updateRun(runId, {
|
|
459
|
-
status:
|
|
446
|
+
status: 'canceled',
|
|
460
447
|
finishedAt: new Date(),
|
|
461
448
|
});
|
|
462
449
|
|
|
463
450
|
this.events.emit({
|
|
464
451
|
runId,
|
|
465
452
|
kind: run.kind,
|
|
466
|
-
eventType:
|
|
453
|
+
eventType: 'run.canceled',
|
|
467
454
|
timestamp: new Date(),
|
|
468
455
|
});
|
|
469
456
|
}
|
|
@@ -478,18 +465,8 @@ export class WorkflowEngine {
|
|
|
478
465
|
return this.storage.getRun(runId);
|
|
479
466
|
}
|
|
480
467
|
|
|
481
|
-
private static readonly TERMINAL_STATUSES = [
|
|
482
|
-
|
|
483
|
-
"failed",
|
|
484
|
-
"canceled",
|
|
485
|
-
"timeout",
|
|
486
|
-
];
|
|
487
|
-
private static readonly TERMINAL_EVENT_TYPES = [
|
|
488
|
-
"run.completed",
|
|
489
|
-
"run.failed",
|
|
490
|
-
"run.canceled",
|
|
491
|
-
"run.timeout",
|
|
492
|
-
];
|
|
468
|
+
private static readonly TERMINAL_STATUSES = ['succeeded', 'failed', 'canceled', 'timeout'];
|
|
469
|
+
private static readonly TERMINAL_EVENT_TYPES = ['run.completed', 'run.failed', 'run.canceled', 'run.timeout'];
|
|
493
470
|
|
|
494
471
|
/**
|
|
495
472
|
* Wait for a run to complete.
|
|
@@ -502,7 +479,7 @@ export class WorkflowEngine {
|
|
|
502
479
|
*/
|
|
503
480
|
async waitForRun(
|
|
504
481
|
runId: string,
|
|
505
|
-
options: { timeout?: number } = {}
|
|
482
|
+
options: { timeout?: number } = {}
|
|
506
483
|
): Promise<WorkflowRunRecord> {
|
|
507
484
|
const timeout = options.timeout ?? 60000;
|
|
508
485
|
|
|
@@ -570,7 +547,7 @@ export class WorkflowEngine {
|
|
|
570
547
|
}
|
|
571
548
|
|
|
572
549
|
// Check if run can be resumed
|
|
573
|
-
if (![
|
|
550
|
+
if (!['queued', 'running'].includes(run.status)) {
|
|
574
551
|
throw new Error(`Cannot resume run ${runId}: status is "${run.status}"`);
|
|
575
552
|
}
|
|
576
553
|
|
|
@@ -588,8 +565,8 @@ export class WorkflowEngine {
|
|
|
588
565
|
|
|
589
566
|
this.logger.info(`Resuming run ${runId} from checkpoint`);
|
|
590
567
|
|
|
591
|
-
// Get completed step keys from the
|
|
592
|
-
const completedStepKeys = new Set(
|
|
568
|
+
// Get completed step keys from the dedicated completedSteps field
|
|
569
|
+
const completedStepKeys = new Set(run.completedSteps ?? []);
|
|
593
570
|
|
|
594
571
|
this.launchRun(runId, definition, run.input, run.metadata, undefined, {
|
|
595
572
|
completedStepKeys,
|
|
@@ -605,7 +582,7 @@ export class WorkflowEngine {
|
|
|
605
582
|
*/
|
|
606
583
|
async getResumableRuns(): Promise<WorkflowRunRecord[]> {
|
|
607
584
|
const result = await this.storage.listRuns({
|
|
608
|
-
status: [
|
|
585
|
+
status: ['queued', 'running'],
|
|
609
586
|
});
|
|
610
587
|
return result.items;
|
|
611
588
|
}
|
|
@@ -630,9 +607,7 @@ export class WorkflowEngine {
|
|
|
630
607
|
this.logger.error(`Failed to resume run ${run.id}:`, error);
|
|
631
608
|
}
|
|
632
609
|
} else {
|
|
633
|
-
this.logger.warn(
|
|
634
|
-
`Cannot resume run ${run.id}: workflow "${run.kind}" not registered`,
|
|
635
|
-
);
|
|
610
|
+
this.logger.warn(`Cannot resume run ${run.id}: workflow "${run.kind}" not registered`);
|
|
636
611
|
}
|
|
637
612
|
}
|
|
638
613
|
|
|
@@ -693,7 +668,7 @@ export class WorkflowEngine {
|
|
|
693
668
|
* Cancels all active runs and closes resources.
|
|
694
669
|
*/
|
|
695
670
|
async shutdown(): Promise<void> {
|
|
696
|
-
this.logger.info(
|
|
671
|
+
this.logger.info('Shutting down workflow engine...');
|
|
697
672
|
|
|
698
673
|
// Cancel all active runs
|
|
699
674
|
for (const [runId, controller] of this.activeRuns) {
|
|
@@ -712,6 +687,6 @@ export class WorkflowEngine {
|
|
|
712
687
|
}
|
|
713
688
|
|
|
714
689
|
this.activeRuns.clear();
|
|
715
|
-
this.logger.info(
|
|
690
|
+
this.logger.info('Workflow engine shutdown complete');
|
|
716
691
|
}
|
|
717
692
|
}
|
package/src/core/index.ts
CHANGED