@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/planning/registry.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Provides storage and retrieval of recipes and handlers.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import RE2 from
|
|
7
|
-
import type { WorkflowStep } from
|
|
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
|
|
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(
|
|
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[
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
209
|
+
results = results.filter(r => r.workflowKind === options.workflowKind);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
if (options.variant) {
|
|
213
|
-
results = results.filter(
|
|
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(
|
|
218
|
-
options.tags!.some(
|
|
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(
|
|
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[
|
|
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(
|
|
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(
|
|
271
|
-
if (current && typeof 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
|
|
288
|
+
case 'eq':
|
|
289
289
|
return fieldValue === value;
|
|
290
290
|
|
|
291
|
-
case
|
|
291
|
+
case 'neq':
|
|
292
292
|
return fieldValue !== value;
|
|
293
293
|
|
|
294
|
-
case
|
|
295
|
-
return typeof fieldValue ===
|
|
294
|
+
case 'gt':
|
|
295
|
+
return typeof fieldValue === 'number' && typeof value === 'number'
|
|
296
296
|
? fieldValue > value
|
|
297
297
|
: false;
|
|
298
298
|
|
|
299
|
-
case
|
|
300
|
-
return typeof fieldValue ===
|
|
299
|
+
case 'gte':
|
|
300
|
+
return typeof fieldValue === 'number' && typeof value === 'number'
|
|
301
301
|
? fieldValue >= value
|
|
302
302
|
: false;
|
|
303
303
|
|
|
304
|
-
case
|
|
305
|
-
return typeof fieldValue ===
|
|
304
|
+
case 'lt':
|
|
305
|
+
return typeof fieldValue === 'number' && typeof value === 'number'
|
|
306
306
|
? fieldValue < value
|
|
307
307
|
: false;
|
|
308
308
|
|
|
309
|
-
case
|
|
310
|
-
return typeof fieldValue ===
|
|
309
|
+
case 'lte':
|
|
310
|
+
return typeof fieldValue === 'number' && typeof value === 'number'
|
|
311
311
|
? fieldValue <= value
|
|
312
312
|
: false;
|
|
313
313
|
|
|
314
|
-
case
|
|
315
|
-
if (typeof fieldValue ===
|
|
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
|
|
324
|
-
if (typeof fieldValue ===
|
|
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
|
|
334
|
-
return
|
|
335
|
-
fieldValue !== undefined && fieldValue !== null && fieldValue !== ""
|
|
336
|
-
);
|
|
333
|
+
case 'exists':
|
|
334
|
+
return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
|
|
337
335
|
|
|
338
|
-
case
|
|
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;
|
package/src/planning/types.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
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
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
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 =
|
|
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>[
|
|
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
|
/**
|