@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
@@ -3,8 +3,8 @@
3
3
  * Provides storage and retrieval of recipes and handlers.
4
4
  */
5
5
 
6
- import RE2 from "re2";
7
- import type { WorkflowStep } from "../core/types";
6
+ import RE2 from 're2';
7
+ import type { WorkflowStep } from '../core/types';
8
8
  import type {
9
9
  Recipe,
10
10
  RecipeCondition,
@@ -13,7 +13,7 @@ import type {
13
13
  RegisteredStepHandler,
14
14
  StepHandlerRegistry,
15
15
  WorkflowKind,
16
- } from "./types";
16
+ } from './types';
17
17
 
18
18
  // ============================================================================
19
19
  // Step Handler Registry Implementation
@@ -28,7 +28,7 @@ export class MemoryStepHandlerRegistry implements StepHandlerRegistry {
28
28
 
29
29
  /** Register a step handler. Throws if a handler with the same ID is already registered. */
30
30
  register<TInput = Record<string, unknown>>(
31
- handler: RegisteredStepHandler<TInput>,
31
+ handler: RegisteredStepHandler<TInput>
32
32
  ): void {
33
33
  if (this.handlers.has(handler.id)) {
34
34
  throw new Error(`Step handler '${handler.id}' is already registered`);
@@ -68,7 +68,7 @@ export class MemoryStepHandlerRegistry implements StepHandlerRegistry {
68
68
  if (!ids) return [];
69
69
 
70
70
  return Array.from(ids)
71
- .map((id) => this.handlers.get(id))
71
+ .map(id => this.handlers.get(id))
72
72
  .filter((h): h is RegisteredStepHandler => h !== undefined);
73
73
  }
74
74
 
@@ -76,7 +76,7 @@ export class MemoryStepHandlerRegistry implements StepHandlerRegistry {
76
76
  * Resolve a handler reference to a WorkflowStep handler function.
77
77
  * Returns undefined if the handler is not found.
78
78
  */
79
- resolve(handlerRef: string): WorkflowStep["handler"] | undefined {
79
+ resolve(handlerRef: string): WorkflowStep['handler'] | undefined {
80
80
  const registered = this.handlers.get(handlerRef);
81
81
  return registered?.handler;
82
82
  }
@@ -121,7 +121,7 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
121
121
  const variantKey = `${recipe.workflowKind}:${recipe.variant}`;
122
122
  if (this.variantIndex.has(variantKey)) {
123
123
  throw new Error(
124
- `Recipe variant '${recipe.variant}' for '${recipe.workflowKind}' is already registered`,
124
+ `Recipe variant '${recipe.variant}' for '${recipe.workflowKind}' is already registered`
125
125
  );
126
126
  }
127
127
  this.variantIndex.set(variantKey, recipe.id);
@@ -160,7 +160,7 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
160
160
  if (!ids) return [];
161
161
 
162
162
  return Array.from(ids)
163
- .map((id) => this.recipes.get(id))
163
+ .map(id => this.recipes.get(id))
164
164
  .filter((r): r is Recipe => r !== undefined);
165
165
  }
166
166
 
@@ -179,7 +179,7 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
179
179
  */
180
180
  getDefault(workflowKind: WorkflowKind): Recipe | undefined {
181
181
  // First try to find a 'default' variant
182
- const defaultRecipe = this.getVariant(workflowKind, "default");
182
+ const defaultRecipe = this.getVariant(workflowKind, 'default');
183
183
  if (defaultRecipe) return defaultRecipe;
184
184
 
185
185
  // Fall back to the recipe with the lowest numeric priority (highest precedence).
@@ -198,7 +198,7 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
198
198
  /** List all variant names registered for a workflow kind. */
199
199
  listVariants(workflowKind: WorkflowKind): string[] {
200
200
  const recipes = this.getByKind(workflowKind);
201
- return recipes.map((r) => r.variant);
201
+ return recipes.map(r => r.variant);
202
202
  }
203
203
 
204
204
  /** Query recipes with optional filters for kind, variant, tags, and input conditions. */
@@ -206,21 +206,21 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
206
206
  let results = this.list();
207
207
 
208
208
  if (options.workflowKind) {
209
- results = results.filter((r) => r.workflowKind === options.workflowKind);
209
+ results = results.filter(r => r.workflowKind === options.workflowKind);
210
210
  }
211
211
 
212
212
  if (options.variant) {
213
- results = results.filter((r) => r.variant === options.variant);
213
+ results = results.filter(r => r.variant === options.variant);
214
214
  }
215
215
 
216
216
  if (options.tags && options.tags.length > 0) {
217
- results = results.filter((r) =>
218
- options.tags!.some((tag) => r.tags?.includes(tag)),
217
+ results = results.filter(r =>
218
+ options.tags!.some(tag => r.tags?.includes(tag))
219
219
  );
220
220
  }
221
221
 
222
222
  if (options.matchConditions) {
223
- results = results.filter((r) => {
223
+ results = results.filter(r => {
224
224
  if (!r.conditions || r.conditions.length === 0) return true;
225
225
  return this.evaluateConditions(r.conditions, options.matchConditions!);
226
226
  });
@@ -252,12 +252,12 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
252
252
  * to keep registry queries independent of the planner implementation.
253
253
  */
254
254
  private evaluateConditions(
255
- conditions: Recipe["conditions"],
256
- input: Record<string, unknown>,
255
+ conditions: Recipe['conditions'],
256
+ input: Record<string, unknown>
257
257
  ): boolean {
258
258
  if (!conditions || conditions.length === 0) return true;
259
259
 
260
- return conditions.every((condition) => {
260
+ return conditions.every(condition => {
261
261
  const fieldValue = this.getNestedValue(input, condition.field);
262
262
  return this.evaluateCondition(condition, fieldValue);
263
263
  });
@@ -267,8 +267,8 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
267
267
  * Get a nested value from an object using dot notation.
268
268
  */
269
269
  private getNestedValue(obj: Record<string, unknown>, path: string): unknown {
270
- return path.split(".").reduce<unknown>((current, key) => {
271
- if (current && typeof current === "object" && key in current) {
270
+ return path.split('.').reduce<unknown>((current, key) => {
271
+ if (current && typeof current === 'object' && key in current) {
272
272
  return (current as Record<string, unknown>)[key];
273
273
  }
274
274
  return undefined;
@@ -280,39 +280,39 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
280
280
  */
281
281
  private evaluateCondition(
282
282
  condition: RecipeCondition,
283
- fieldValue: unknown,
283
+ fieldValue: unknown
284
284
  ): boolean {
285
285
  const { operator, value } = condition;
286
286
 
287
287
  switch (operator) {
288
- case "eq":
288
+ case 'eq':
289
289
  return fieldValue === value;
290
290
 
291
- case "neq":
291
+ case 'neq':
292
292
  return fieldValue !== value;
293
293
 
294
- case "gt":
295
- return typeof fieldValue === "number" && typeof value === "number"
294
+ case 'gt':
295
+ return typeof fieldValue === 'number' && typeof value === 'number'
296
296
  ? fieldValue > value
297
297
  : false;
298
298
 
299
- case "gte":
300
- return typeof fieldValue === "number" && typeof value === "number"
299
+ case 'gte':
300
+ return typeof fieldValue === 'number' && typeof value === 'number'
301
301
  ? fieldValue >= value
302
302
  : false;
303
303
 
304
- case "lt":
305
- return typeof fieldValue === "number" && typeof value === "number"
304
+ case 'lt':
305
+ return typeof fieldValue === 'number' && typeof value === 'number'
306
306
  ? fieldValue < value
307
307
  : false;
308
308
 
309
- case "lte":
310
- return typeof fieldValue === "number" && typeof value === "number"
309
+ case 'lte':
310
+ return typeof fieldValue === 'number' && typeof value === 'number'
311
311
  ? fieldValue <= value
312
312
  : false;
313
313
 
314
- case "contains":
315
- if (typeof fieldValue === "string" && typeof value === "string") {
314
+ case 'contains':
315
+ if (typeof fieldValue === 'string' && typeof value === 'string') {
316
316
  return fieldValue.includes(value);
317
317
  }
318
318
  if (Array.isArray(fieldValue)) {
@@ -320,8 +320,8 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
320
320
  }
321
321
  return false;
322
322
 
323
- case "matches":
324
- if (typeof fieldValue === "string" && typeof value === "string") {
323
+ case 'matches':
324
+ if (typeof fieldValue === 'string' && typeof value === 'string') {
325
325
  try {
326
326
  return new RE2(value).test(fieldValue);
327
327
  } catch {
@@ -330,15 +330,11 @@ export class MemoryRecipeRegistry implements RecipeRegistry {
330
330
  }
331
331
  return false;
332
332
 
333
- case "exists":
334
- return (
335
- fieldValue !== undefined && fieldValue !== null && fieldValue !== ""
336
- );
333
+ case 'exists':
334
+ return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
337
335
 
338
- case "notExists":
339
- return (
340
- fieldValue === undefined || fieldValue === null || fieldValue === ""
341
- );
336
+ case 'notExists':
337
+ return fieldValue === undefined || fieldValue === null || fieldValue === '';
342
338
 
343
339
  default:
344
340
  return false;
@@ -3,14 +3,10 @@
3
3
  * Enables dynamic workflow orchestration through recipes and plans.
4
4
  */
5
5
 
6
- import type {
7
- WorkflowKind,
8
- StepErrorStrategy,
9
- WorkflowStep,
10
- } from "../core/types";
6
+ import type { WorkflowKind, StepErrorStrategy, WorkflowStep } from '../core/types';
11
7
 
12
8
  // Re-export core types used in planning
13
- export type { WorkflowKind, StepErrorStrategy } from "../core/types";
9
+ export type { WorkflowKind, StepErrorStrategy } from '../core/types';
14
10
 
15
11
  // ============================================================================
16
12
  // Recipe Types
@@ -20,16 +16,16 @@ export type { WorkflowKind, StepErrorStrategy } from "../core/types";
20
16
  * Comparison operators for recipe conditions.
21
17
  */
22
18
  export type ConditionOperator =
23
- | "eq" // equals
24
- | "neq" // not equals
25
- | "gt" // greater than
26
- | "gte" // greater than or equal
27
- | "lt" // less than
28
- | "lte" // less than or equal
29
- | "contains" // string/array contains
30
- | "matches" // regex match
31
- | "exists" // field exists and is truthy
32
- | "notExists"; // field does not exist or is falsy
19
+ | 'eq' // equals
20
+ | 'neq' // not equals
21
+ | 'gt' // greater than
22
+ | 'gte' // greater than or equal
23
+ | 'lt' // less than
24
+ | 'lte' // less than or equal
25
+ | 'contains' // string/array contains
26
+ | 'matches' // regex match
27
+ | 'exists' // field exists and is truthy
28
+ | 'notExists'; // field does not exist or is falsy
33
29
 
34
30
  /**
35
31
  * A condition that determines when a recipe should be selected.
@@ -169,11 +165,11 @@ export interface Recipe {
169
165
  * Types of modifications that can be made to a plan.
170
166
  */
171
167
  export type PlanModificationType =
172
- | "add_step" // Add a new step
173
- | "remove_step" // Remove an existing step
174
- | "modify_step" // Modify step configuration
175
- | "reorder_steps" // Change step order
176
- | "set_default"; // Override a default parameter
168
+ | 'add_step' // Add a new step
169
+ | 'remove_step' // Remove an existing step
170
+ | 'modify_step' // Modify step configuration
171
+ | 'reorder_steps' // Change step order
172
+ | 'set_default'; // Override a default parameter
177
173
 
178
174
  /**
179
175
  * A modification to apply to a recipe when generating a plan.
@@ -306,7 +302,7 @@ export interface Plan {
306
302
  /**
307
303
  * Priority modes for planning.
308
304
  */
309
- export type PlanningPriority = "speed" | "quality" | "cost" | "balanced";
305
+ export type PlanningPriority = 'speed' | 'quality' | 'cost' | 'balanced';
310
306
 
311
307
  /**
312
308
  * Constraints that limit planning and execution.
@@ -407,7 +403,7 @@ export interface Planner {
407
403
  selectRecipe(
408
404
  workflowKind: WorkflowKind,
409
405
  input: Record<string, unknown>,
410
- context?: PlanningContext,
406
+ context?: PlanningContext
411
407
  ): Promise<RecipeSelectionResult>;
412
408
 
413
409
  /**
@@ -416,7 +412,7 @@ export interface Planner {
416
412
  generatePlan(
417
413
  recipe: Recipe,
418
414
  input: Record<string, unknown>,
419
- context?: PlanningContext,
415
+ context?: PlanningContext
420
416
  ): Promise<Plan>;
421
417
 
422
418
  /**
@@ -425,7 +421,7 @@ export interface Planner {
425
421
  plan(
426
422
  workflowKind: WorkflowKind,
427
423
  input: Record<string, unknown>,
428
- context?: PlanningContext,
424
+ context?: PlanningContext
429
425
  ): Promise<Plan>;
430
426
 
431
427
  /**
@@ -454,7 +450,7 @@ export interface RegisteredStepHandler<TInput = Record<string, unknown>> {
454
450
  description?: string;
455
451
 
456
452
  /** The handler function */
457
- handler: WorkflowStep<TInput>["handler"];
453
+ handler: WorkflowStep<TInput>['handler'];
458
454
 
459
455
  /** Tags for categorization */
460
456
  tags?: string[];
@@ -469,7 +465,7 @@ export interface StepHandlerRegistry {
469
465
  * Register a step handler.
470
466
  */
471
467
  register<TInput = Record<string, unknown>>(
472
- handler: RegisteredStepHandler<TInput>,
468
+ handler: RegisteredStepHandler<TInput>
473
469
  ): void;
474
470
 
475
471
  /**