@multiplier-labs/stepflow 0.3.3 → 0.3.4
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/code-review/2026-04-11_code-review-daily.md +146 -0
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
- package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
- package/.github/workflows/publish.yml +6 -0
- package/dist/{chunk-4VOKMGP6.js → chunk-5XJBAPXF.js} +517 -522
- package/dist/chunk-5XJBAPXF.js.map +1 -0
- package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
- package/dist/chunk-FMPJBK2N.js.map +1 -0
- package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
- package/dist/chunk-JPXPB7U5.js.map +1 -0
- package/dist/{chunk-GYPNJBDP.js → chunk-U3PCVK6O.js} +266 -47
- package/dist/chunk-U3PCVK6O.js.map +1 -0
- package/dist/events/index.d.ts +56 -3
- package/dist/events/index.js +1 -1
- package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
- package/dist/index.d.ts +2 -12
- package/dist/index.js +764 -485
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/index.js +2 -2
- package/dist/storage/index.d.ts +313 -47
- package/dist/storage/index.js +4 -2
- package/docs/stepflow-api-reference.md +3 -1
- package/package.json +7 -4
- package/src/core/engine/concurrency.ts +229 -0
- package/src/core/engine/lifecycle.ts +176 -0
- package/src/core/engine.test.ts +70 -0
- package/src/core/engine.ts +91 -185
- package/src/core/orchestrator/persistence.ts +165 -0
- package/src/core/orchestrator/plan-resolution.ts +140 -0
- package/src/core/orchestrator/state-transitions.ts +109 -0
- package/src/core/orchestrator/step-runner.ts +427 -0
- package/src/core/orchestrator.ts +40 -359
- package/src/events/webhook.test.ts +292 -0
- package/src/events/webhook.ts +320 -77
- package/src/planning/evaluate-condition.test.ts +153 -0
- package/src/planning/evaluate-condition.ts +105 -0
- package/src/planning/planner.ts +1 -83
- package/src/planning/registry.ts +3 -85
- package/src/scheduler/cron.test.ts +9 -1
- package/src/scheduler/cron.ts +26 -2
- package/src/scheduler/postgres-persistence.ts +27 -15
- package/src/scheduler/sqlite-persistence.ts +28 -3
- package/src/storage/postgres-core.ts +550 -0
- package/src/storage/postgres-migration.test.ts +69 -0
- package/src/storage/postgres-transaction-pagination.test.ts +107 -0
- package/src/storage/postgres.ts +171 -690
- package/src/storage/sqlite-core.ts +151 -0
- package/src/storage/sqlite.test.ts +177 -1
- package/src/storage/sqlite.ts +183 -93
- package/src/utils/index.ts +2 -0
- package/src/utils/pinned-fetch.test.ts +84 -0
- package/src/utils/pinned-fetch.ts +118 -0
- package/src/utils/postgres-deps.ts +16 -0
- package/src/utils/safe-json.test.ts +166 -0
- package/src/utils/safe-json.ts +107 -0
- package/src/utils/webhook-signature.test.ts +111 -0
- package/src/utils/webhook-signature.ts +74 -0
- package/dist/chunk-4VOKMGP6.js.map +0 -1
- package/dist/chunk-GYPNJBDP.js.map +0 -1
- package/dist/chunk-KHTUWLVZ.js.map +0 -1
- package/dist/chunk-ML35PIHX.js.map +0 -1
package/dist/events/index.d.ts
CHANGED
|
@@ -215,6 +215,23 @@ interface WebhookEventTransportConfig {
|
|
|
215
215
|
* Requests beyond this limit are queued.
|
|
216
216
|
*/
|
|
217
217
|
maxConcurrentRequests?: number;
|
|
218
|
+
/**
|
|
219
|
+
* Maximum number of queued webhook deliveries waiting for a concurrency slot
|
|
220
|
+
* (default: 1000, env override: `STEPFLOW_WEBHOOK_MAX_QUEUE_DEPTH`).
|
|
221
|
+
*
|
|
222
|
+
* When the queue is full, incoming `emit()` deliveries are dropped to
|
|
223
|
+
* protect the process from memory exhaustion under burst traffic. Drops
|
|
224
|
+
* are logged via `logger.warn` with the endpoint id, current queue depth,
|
|
225
|
+
* and a recommended `retryAfterMs`. A value of `0` disables the cap (not
|
|
226
|
+
* recommended in production).
|
|
227
|
+
*/
|
|
228
|
+
maxQueueDepth?: number;
|
|
229
|
+
/**
|
|
230
|
+
* Suggested back-off in milliseconds reported on dropped deliveries
|
|
231
|
+
* (default: 1000). Surfaced in the warning log so external orchestrators
|
|
232
|
+
* (sidecars, sweeper jobs) can respect it like an HTTP `Retry-After`.
|
|
233
|
+
*/
|
|
234
|
+
queueDropRetryAfterMs?: number;
|
|
218
235
|
/** Logger for webhook transport errors (default: console-based) */
|
|
219
236
|
logger?: Logger;
|
|
220
237
|
}
|
|
@@ -267,12 +284,25 @@ declare class WebhookEventTransport implements EventTransport {
|
|
|
267
284
|
private defaultRetries;
|
|
268
285
|
private retryDelay;
|
|
269
286
|
private fetchFn;
|
|
287
|
+
/**
|
|
288
|
+
* Whether `fetchFn` was caller-supplied. When `false` the default fetch is
|
|
289
|
+
* replaced with `pinnedFetch` so the TCP connection can target the IP that
|
|
290
|
+
* was just validated (closes the DNS-rebinding TOCTOU window). When `true`
|
|
291
|
+
* the custom fetch is used as-is so test injection still works.
|
|
292
|
+
*/
|
|
293
|
+
private hasCustomFetch;
|
|
270
294
|
private allowInsecureUrls;
|
|
271
295
|
private maxPayloadBytes;
|
|
272
296
|
private maxConcurrentRequests;
|
|
297
|
+
private maxQueueDepth;
|
|
298
|
+
private queueDropRetryAfterMs;
|
|
273
299
|
private activeRequests;
|
|
274
300
|
private requestQueue;
|
|
301
|
+
private droppedRequests;
|
|
275
302
|
private logger;
|
|
303
|
+
private dnsCache;
|
|
304
|
+
private static readonly DNS_CACHE_TTL_MS;
|
|
305
|
+
private static readonly DNS_TIMEOUT_MS;
|
|
276
306
|
private runSubscribers;
|
|
277
307
|
private globalSubscribers;
|
|
278
308
|
constructor(config?: WebhookEventTransportConfig);
|
|
@@ -298,6 +328,15 @@ declare class WebhookEventTransport implements EventTransport {
|
|
|
298
328
|
* Emit an event to all matching webhook endpoints.
|
|
299
329
|
*/
|
|
300
330
|
emit(event: WorkflowEvent): void;
|
|
331
|
+
/**
|
|
332
|
+
* Number of webhook deliveries dropped due to queue overflow since startup.
|
|
333
|
+
* Useful for surfacing back-pressure to operators (metrics/health checks).
|
|
334
|
+
*/
|
|
335
|
+
getDroppedRequestCount(): number;
|
|
336
|
+
/**
|
|
337
|
+
* Current depth of the pending request queue.
|
|
338
|
+
*/
|
|
339
|
+
getQueueDepth(): number;
|
|
301
340
|
/**
|
|
302
341
|
* Subscribe to events for a specific run (server-side).
|
|
303
342
|
*/
|
|
@@ -327,8 +366,18 @@ declare class WebhookEventTransport implements EventTransport {
|
|
|
327
366
|
*/
|
|
328
367
|
private sleep;
|
|
329
368
|
/**
|
|
330
|
-
* Resolve
|
|
331
|
-
*
|
|
369
|
+
* Resolve a webhook URL's hostname exactly once, verify the resolved IP is
|
|
370
|
+
* not private/reserved, and return the address + family so the caller can
|
|
371
|
+
* pin the connection to it.
|
|
372
|
+
*
|
|
373
|
+
* Returns `null` for raw-IP URLs (already validated by `validateWebhookUrl`).
|
|
374
|
+
*
|
|
375
|
+
* Pinning the resolved IP closes the DNS rebinding TOCTOU window (audit M1):
|
|
376
|
+
* with the original implementation `fetch()` re-resolved the hostname,
|
|
377
|
+
* giving a hostile DNS server a chance to flip the answer to an internal
|
|
378
|
+
* address between SSRF validation and connect.
|
|
379
|
+
*
|
|
380
|
+
* Uses a short-TTL cache and per-call timeout to avoid blocking the event loop.
|
|
332
381
|
*/
|
|
333
382
|
private validateResolvedHost;
|
|
334
383
|
/**
|
|
@@ -338,7 +387,11 @@ declare class WebhookEventTransport implements EventTransport {
|
|
|
338
387
|
*/
|
|
339
388
|
private validateWebhookUrl;
|
|
340
389
|
/**
|
|
341
|
-
* Enqueue a webhook request with concurrency limiting.
|
|
390
|
+
* Enqueue a webhook request with concurrency limiting and a queue-depth cap.
|
|
391
|
+
* Returns `true` if the request was accepted (executed or queued), or
|
|
392
|
+
* `false` if the queue is full and the request was rejected. The caller is
|
|
393
|
+
* responsible for logging the drop and incrementing any drop metrics, since
|
|
394
|
+
* it has the endpoint context.
|
|
342
395
|
*/
|
|
343
396
|
private enqueueRequest;
|
|
344
397
|
}
|
package/dist/events/index.js
CHANGED
|
@@ -10,6 +10,10 @@ import { Pool, PoolConfig } from 'pg';
|
|
|
10
10
|
* This is the primary entry point for the workflow engine.
|
|
11
11
|
* It provides methods for registering workflows, starting runs,
|
|
12
12
|
* and managing workflow execution.
|
|
13
|
+
*
|
|
14
|
+
* Internal concerns are decomposed into co-located submodules under
|
|
15
|
+
* `./engine/` (see `engine/concurrency.ts`, `engine/lifecycle.ts`).
|
|
16
|
+
* WorkflowEngine itself stays the thin public face.
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
19
|
/**
|
|
@@ -47,14 +51,40 @@ interface StartRunOptions<TInput = Record<string, unknown>> {
|
|
|
47
51
|
/** Priority for queue ordering (higher = runs first, default: 0) */
|
|
48
52
|
priority?: number;
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Main workflow engine class.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const engine = new WorkflowEngine({
|
|
60
|
+
* storage: new MemoryStorageAdapter(),
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* engine.registerWorkflow({
|
|
64
|
+
* kind: 'my.workflow',
|
|
65
|
+
* name: 'My Workflow',
|
|
66
|
+
* steps: [
|
|
67
|
+
* {
|
|
68
|
+
* key: 'step1',
|
|
69
|
+
* name: 'First Step',
|
|
70
|
+
* handler: async (ctx) => {
|
|
71
|
+
* return { message: 'Hello!' };
|
|
72
|
+
* },
|
|
73
|
+
* },
|
|
74
|
+
* ],
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* const runId = await engine.startRun({ kind: 'my.workflow' });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
50
80
|
declare class WorkflowEngine {
|
|
51
81
|
private registry;
|
|
52
82
|
private storage;
|
|
53
83
|
private events;
|
|
54
84
|
private logger;
|
|
55
|
-
private activeRuns;
|
|
56
85
|
private settings;
|
|
57
|
-
private
|
|
86
|
+
private concurrency;
|
|
87
|
+
private lifecycle;
|
|
58
88
|
constructor(config?: WorkflowEngineConfig);
|
|
59
89
|
/**
|
|
60
90
|
* Initialize the engine and its storage/event adapters.
|
|
@@ -70,10 +100,6 @@ declare class WorkflowEngine {
|
|
|
70
100
|
* Get the number of queued runs waiting for capacity.
|
|
71
101
|
*/
|
|
72
102
|
getQueuedRunCount(): number;
|
|
73
|
-
/**
|
|
74
|
-
* Check if capacity is available for a new run.
|
|
75
|
-
*/
|
|
76
|
-
private hasCapacity;
|
|
77
103
|
/**
|
|
78
104
|
* Register a workflow definition.
|
|
79
105
|
* Must be called before runs of this type can be started.
|
|
@@ -110,10 +136,6 @@ declare class WorkflowEngine {
|
|
|
110
136
|
* @throws WorkflowNotFoundError if the workflow kind is not registered
|
|
111
137
|
*/
|
|
112
138
|
startRun<TInput = Record<string, unknown>>(options: StartRunOptions<TInput>): Promise<string>;
|
|
113
|
-
/**
|
|
114
|
-
* Queue a run in priority order.
|
|
115
|
-
*/
|
|
116
|
-
private queueRun;
|
|
117
139
|
/**
|
|
118
140
|
* Execute a run (internal method).
|
|
119
141
|
*/
|
|
@@ -124,7 +146,8 @@ declare class WorkflowEngine {
|
|
|
124
146
|
*/
|
|
125
147
|
private launchRun;
|
|
126
148
|
/**
|
|
127
|
-
*
|
|
149
|
+
* Drain the queue, dispatching each ready run via `executeRun`. Resilience
|
|
150
|
+
* against per-item dispatch failures lives in `ConcurrencyManager.processQueue`.
|
|
128
151
|
*/
|
|
129
152
|
private processQueue;
|
|
130
153
|
/**
|
|
@@ -415,6 +438,12 @@ interface SQLiteSchedulePersistenceConfig {
|
|
|
415
438
|
db: Database;
|
|
416
439
|
/** Table name for schedules (default: workflow_schedules) */
|
|
417
440
|
tableName?: string;
|
|
441
|
+
/**
|
|
442
|
+
* Optional structured logger used to surface JSON corruption events.
|
|
443
|
+
* Without one, parse failures still increment the corruption counter
|
|
444
|
+
* exposed via `getJsonParseCorruptionCount()` but produce no log output.
|
|
445
|
+
*/
|
|
446
|
+
logger?: Logger;
|
|
418
447
|
}
|
|
419
448
|
/**
|
|
420
449
|
* SQLite-based persistence for workflow schedules.
|
|
@@ -435,6 +464,7 @@ interface SQLiteSchedulePersistenceConfig {
|
|
|
435
464
|
declare class SQLiteSchedulePersistence implements SchedulePersistence {
|
|
436
465
|
private db;
|
|
437
466
|
private tableName;
|
|
467
|
+
private logger;
|
|
438
468
|
private stmts;
|
|
439
469
|
constructor(config: SQLiteSchedulePersistenceConfig);
|
|
440
470
|
private initializeDatabase;
|
|
@@ -442,6 +472,7 @@ declare class SQLiteSchedulePersistence implements SchedulePersistence {
|
|
|
442
472
|
saveSchedule(schedule: WorkflowSchedule): Promise<void>;
|
|
443
473
|
updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void>;
|
|
444
474
|
deleteSchedule(scheduleId: string): Promise<void>;
|
|
475
|
+
private safeJsonParse;
|
|
445
476
|
private rowToSchedule;
|
|
446
477
|
}
|
|
447
478
|
|
|
@@ -484,6 +515,12 @@ interface PostgresSchedulePersistenceConfig {
|
|
|
484
515
|
* @default true
|
|
485
516
|
*/
|
|
486
517
|
autoMigrate?: boolean;
|
|
518
|
+
/**
|
|
519
|
+
* Optional structured logger used to surface JSON corruption events.
|
|
520
|
+
* Without one, parse failures still increment the corruption counter
|
|
521
|
+
* exposed via `getJsonParseCorruptionCount()` but produce no log output.
|
|
522
|
+
*/
|
|
523
|
+
logger?: Logger;
|
|
487
524
|
}
|
|
488
525
|
/**
|
|
489
526
|
* PostgreSQL-based persistence for workflow schedules.
|
|
@@ -523,6 +560,7 @@ declare class PostgresSchedulePersistence implements SchedulePersistence {
|
|
|
523
560
|
private autoMigrate;
|
|
524
561
|
private initialized;
|
|
525
562
|
private config;
|
|
563
|
+
private logger;
|
|
526
564
|
constructor(config: PostgresSchedulePersistenceConfig);
|
|
527
565
|
/**
|
|
528
566
|
* Get a schema-scoped query builder.
|
|
@@ -561,6 +599,7 @@ declare class PostgresSchedulePersistence implements SchedulePersistence {
|
|
|
561
599
|
* Get workflow completion triggers for a specific workflow kind.
|
|
562
600
|
*/
|
|
563
601
|
getCompletionTriggers(triggerOnWorkflowKind: string): Promise<WorkflowSchedule[]>;
|
|
602
|
+
private safeParseOptionalField;
|
|
564
603
|
private rowToSchedule;
|
|
565
604
|
}
|
|
566
605
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { a as WorkflowError, L as Logger, W as WorkflowKind, b as StepErrorStrategy, c as WorkflowStep } from './types-K5Gjk3H_.js';
|
|
2
2
|
export { d as RunResult, R as RunStatus, e as SpawnChildOptions, S as StepStatus, f as WorkflowContext, g as WorkflowDefinition, h as WorkflowHooks } from './types-K5Gjk3H_.js';
|
|
3
|
-
export { C as CronScheduler, a as CronSchedulerConfig, P as PostgresSchedulePersistence, b as PostgresSchedulePersistenceConfig, S as SQLiteSchedulePersistence, c as SQLiteSchedulePersistenceConfig, d as SchedulePersistence, e as Scheduler, f as StartRunOptions, T as TriggerType, W as WorkflowEngine, g as WorkflowEngineConfig, h as WorkflowSchedule } from './index-
|
|
3
|
+
export { C as CronScheduler, a as CronSchedulerConfig, P as PostgresSchedulePersistence, b as PostgresSchedulePersistenceConfig, S as SQLiteSchedulePersistence, c as SQLiteSchedulePersistenceConfig, d as SchedulePersistence, e as Scheduler, f as StartRunOptions, T as TriggerType, W as WorkflowEngine, g as WorkflowEngineConfig, h as WorkflowSchedule } from './index-Cd0IZ40L.js';
|
|
4
4
|
export { C as CreateRunInput, E as ExtendedListRunsOptions, a as ExtendedRunStatus, b as ExtendedStepStatus, c as ExtendedWorkflowRunRecord, L as ListEventsOptions, d as ListRunsOptions, P as PaginatedResult, S as StepRecord, e as StepResult, f as StepflowDatabase, g as StepflowRunsTable, h as StepflowStepResultsTable, i as StorageAdapter, U as UpdateRunInput, W as WorkflowEventRecord, j as WorkflowRunRecord, k as WorkflowRunStepRecord, l as WorkflowStorage } from './types-DSEjiLMj.js';
|
|
5
5
|
export { MemoryStorageAdapter, PostgresStorage, PostgresStorage as PostgresStorageAdapter, PostgresStorageConfig, SQLiteStorageAdapter, SQLiteStorageConfig } from './storage/index.js';
|
|
6
6
|
export { B as BuiltInEventType, E as EventCallback, a as EventTransport, U as Unsubscribe, W as WorkflowEvent, b as WorkflowEventType } from './types-DZUdWmCc.js';
|
|
7
7
|
export { MemoryEventTransport, SocketIOAuthorizeFn, SocketIOEventTransport, SocketIOEventTransportConfig, SocketIOServer, SocketIOSocket, WebhookEndpoint, WebhookEventTransport, WebhookEventTransportConfig, WebhookPayload } from './events/index.js';
|
|
8
8
|
import 'better-sqlite3';
|
|
9
9
|
import 'pg';
|
|
10
|
+
import 'kysely';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* ID generation utilities for the workflow engine.
|
|
@@ -647,19 +648,8 @@ declare class MemoryRecipeRegistry implements RecipeRegistry {
|
|
|
647
648
|
/**
|
|
648
649
|
* Evaluate recipe conditions against an input.
|
|
649
650
|
* Returns true if all conditions match.
|
|
650
|
-
*
|
|
651
|
-
* Note: This intentionally mirrors the condition evaluation in planner.ts
|
|
652
|
-
* to keep registry queries independent of the planner implementation.
|
|
653
651
|
*/
|
|
654
652
|
private evaluateConditions;
|
|
655
|
-
/**
|
|
656
|
-
* Get a nested value from an object using dot notation.
|
|
657
|
-
*/
|
|
658
|
-
private getNestedValue;
|
|
659
|
-
/**
|
|
660
|
-
* Evaluate a single condition.
|
|
661
|
-
*/
|
|
662
|
-
private evaluateCondition;
|
|
663
653
|
}
|
|
664
654
|
/**
|
|
665
655
|
* Create a combined registry that holds both recipes and step handlers.
|