@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.
Files changed (84) hide show
  1. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
  2. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
  3. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
  4. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
  5. package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
  6. package/.github/CODEOWNERS +15 -0
  7. package/.github/dependabot.yml +23 -0
  8. package/.github/workflows/ci.yml +38 -0
  9. package/.github/workflows/publish.yml +11 -5
  10. package/.github/workflows/security.yml +29 -0
  11. package/CHANGELOG.md +34 -0
  12. package/README.md +24 -0
  13. package/SECURITY.md +40 -0
  14. package/dist/{chunk-FQCT3I2A.js → chunk-4VOKMGP6.js} +23 -42
  15. package/dist/chunk-4VOKMGP6.js.map +1 -0
  16. package/dist/{chunk-XCF4PLN7.js → chunk-GYPNJBDP.js} +12 -16
  17. package/dist/chunk-GYPNJBDP.js.map +1 -0
  18. package/dist/{chunk-U5QGN3RT.js → chunk-KHTUWLVZ.js} +14 -47
  19. package/dist/chunk-KHTUWLVZ.js.map +1 -0
  20. package/dist/{chunk-GXY66V7Q.js → chunk-ML35PIHX.js} +1 -1
  21. package/dist/chunk-ML35PIHX.js.map +1 -0
  22. package/dist/events/index.d.ts +4 -4
  23. package/dist/events/index.js +1 -1
  24. package/dist/{index-CPfGsmrp.d.ts → index-Dzr437iW.d.ts} +6 -6
  25. package/dist/index.d.ts +14 -12
  26. package/dist/index.js +40 -82
  27. package/dist/index.js.map +1 -1
  28. package/dist/scheduler/index.d.ts +4 -4
  29. package/dist/scheduler/index.js +2 -2
  30. package/dist/storage/index.d.ts +13 -13
  31. package/dist/storage/index.js +2 -2
  32. package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
  33. package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
  34. package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
  35. package/docs/stepflow-api-reference.md +2 -3
  36. package/package.json +7 -6
  37. package/src/core/engine.test.ts +466 -522
  38. package/src/core/engine.ts +42 -67
  39. package/src/core/index.ts +3 -3
  40. package/src/core/orchestrator.ts +64 -83
  41. package/src/core/types.ts +7 -29
  42. package/src/events/index.ts +4 -4
  43. package/src/events/memory.test.ts +39 -41
  44. package/src/events/memory.ts +7 -10
  45. package/src/events/socketio.test.ts +112 -162
  46. package/src/events/socketio.ts +20 -27
  47. package/src/events/types.ts +16 -19
  48. package/src/events/webhook.test.ts +197 -235
  49. package/src/events/webhook.ts +44 -71
  50. package/src/index.ts +19 -19
  51. package/src/planning/index.ts +6 -3
  52. package/src/planning/planner.ts +66 -91
  53. package/src/planning/planning.test.ts +303 -366
  54. package/src/planning/registry.test.ts +204 -251
  55. package/src/planning/registry.ts +39 -43
  56. package/src/planning/types.ts +23 -27
  57. package/src/scheduler/cron.test.ts +141 -150
  58. package/src/scheduler/cron.ts +32 -67
  59. package/src/scheduler/index.ts +4 -14
  60. package/src/scheduler/postgres-persistence.ts +71 -99
  61. package/src/scheduler/sqlite-persistence.test.ts +96 -96
  62. package/src/scheduler/sqlite-persistence.ts +12 -19
  63. package/src/scheduler/types.ts +4 -9
  64. package/src/storage/index.ts +5 -5
  65. package/src/storage/memory.test.ts +133 -183
  66. package/src/storage/memory.ts +21 -42
  67. package/src/storage/postgres.ts +197 -393
  68. package/src/storage/sqlite.test.ts +154 -281
  69. package/src/storage/sqlite.ts +50 -85
  70. package/src/storage/types.ts +33 -58
  71. package/src/utils/errors.test.ts +78 -80
  72. package/src/utils/errors.ts +27 -57
  73. package/src/utils/id.ts +3 -3
  74. package/src/utils/index.ts +4 -4
  75. package/src/utils/logger.test.ts +52 -58
  76. package/src/utils/logger.ts +4 -4
  77. package/src/utils/postgres-deps.ts +4 -4
  78. package/src/utils/retry.test.ts +47 -67
  79. package/src/utils/retry.ts +9 -9
  80. package/dist/chunk-FQCT3I2A.js.map +0 -1
  81. package/dist/chunk-GXY66V7Q.js.map +0 -1
  82. package/dist/chunk-U5QGN3RT.js.map +0 -1
  83. package/dist/chunk-XCF4PLN7.js.map +0 -1
  84. package/src/scheduler/postgres-persistence.test.ts +0 -906
@@ -12,24 +12,19 @@ import type {
12
12
  WorkflowContext,
13
13
  Logger,
14
14
  SpawnChildOptions,
15
- } from "./types";
16
- import type { StorageAdapter, WorkflowRunRecord } from "../storage/types";
17
- import type {
18
- EventTransport,
19
- EventCallback,
20
- Unsubscribe,
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 "../utils/errors";
32
- import { ConsoleLogger } from "../utils/logger";
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["settings"]>;
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: "queued",
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: "run.created",
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: "run.queued",
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: "run.dequeued",
388
+ eventType: 'run.dequeued',
404
389
  timestamp: new Date(),
405
390
  });
406
391
 
407
- this.executeRun(next.runId, next.definition, next.input, next.metadata);
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 (["succeeded", "failed", "canceled", "timeout"].includes(run.status)) {
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((q) => q.runId === runId);
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: "canceled",
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: "run.canceled",
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
- "succeeded",
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 (!["queued", "running"].includes(run.status)) {
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 context
592
- const completedStepKeys = new Set(Object.keys(run.context));
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: ["queued", "running"],
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("Shutting down workflow engine...");
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("Workflow engine shutdown complete");
690
+ this.logger.info('Workflow engine shutdown complete');
716
691
  }
717
692
  }
package/src/core/index.ts CHANGED
@@ -2,6 +2,6 @@
2
2
  * Core module exports.
3
3
  */
4
4
 
5
- export * from "./types";
6
- export * from "./engine";
7
- export * from "./orchestrator";
5
+ export * from './types';
6
+ export * from './engine';
7
+ export * from './orchestrator';