@multiplier-labs/stepflow 0.3.4 → 0.3.6

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 (99) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-U3PCVK6O.js → chunk-D5HW7VEG.js} +4 -2
  3. package/dist/chunk-D5HW7VEG.js.map +1 -0
  4. package/dist/{chunk-5XJBAPXF.js → chunk-JUPZEBR3.js} +3 -1
  5. package/dist/{chunk-5XJBAPXF.js.map → chunk-JUPZEBR3.js.map} +1 -1
  6. package/dist/events/index.d.ts +2 -0
  7. package/dist/events/index.js +1 -1
  8. package/dist/index.js +2 -2
  9. package/dist/storage/index.js +1 -1
  10. package/package.json +23 -12
  11. package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
  12. package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
  13. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  14. package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
  15. package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
  16. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  17. package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
  18. package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
  19. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  20. package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
  21. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  22. package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
  23. package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
  24. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  25. package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
  26. package/.github/CODEOWNERS +0 -15
  27. package/.github/dependabot.yml +0 -23
  28. package/.github/workflows/ci.yml +0 -38
  29. package/.github/workflows/publish.yml +0 -42
  30. package/.github/workflows/security.yml +0 -29
  31. package/CHANGELOG.md +0 -34
  32. package/SECURITY.md +0 -40
  33. package/dist/chunk-U3PCVK6O.js.map +0 -1
  34. package/docs/github-packages-setup.md +0 -97
  35. package/docs/stepflow-api-reference.md +0 -1663
  36. package/src/core/engine/concurrency.ts +0 -229
  37. package/src/core/engine/lifecycle.ts +0 -176
  38. package/src/core/engine.test.ts +0 -1435
  39. package/src/core/engine.ts +0 -598
  40. package/src/core/index.ts +0 -7
  41. package/src/core/orchestrator/persistence.ts +0 -165
  42. package/src/core/orchestrator/plan-resolution.ts +0 -140
  43. package/src/core/orchestrator/state-transitions.ts +0 -109
  44. package/src/core/orchestrator/step-runner.ts +0 -427
  45. package/src/core/orchestrator.ts +0 -252
  46. package/src/core/types.ts +0 -220
  47. package/src/events/index.ts +0 -8
  48. package/src/events/memory.test.ts +0 -107
  49. package/src/events/memory.ts +0 -92
  50. package/src/events/socketio.test.ts +0 -378
  51. package/src/events/socketio.ts +0 -262
  52. package/src/events/types.ts +0 -118
  53. package/src/events/webhook.test.ts +0 -949
  54. package/src/events/webhook.ts +0 -807
  55. package/src/index.ts +0 -270
  56. package/src/planning/evaluate-condition.test.ts +0 -153
  57. package/src/planning/evaluate-condition.ts +0 -105
  58. package/src/planning/index.ts +0 -55
  59. package/src/planning/planner.ts +0 -494
  60. package/src/planning/planning.test.ts +0 -727
  61. package/src/planning/registry.test.ts +0 -398
  62. package/src/planning/registry.ts +0 -283
  63. package/src/planning/types.ts +0 -566
  64. package/src/scheduler/cron.test.ts +0 -479
  65. package/src/scheduler/cron.ts +0 -438
  66. package/src/scheduler/index.ts +0 -8
  67. package/src/scheduler/postgres-persistence.ts +0 -530
  68. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  69. package/src/scheduler/sqlite-persistence.ts +0 -286
  70. package/src/scheduler/types.ts +0 -73
  71. package/src/storage/index.ts +0 -27
  72. package/src/storage/memory.test.ts +0 -349
  73. package/src/storage/memory.ts +0 -227
  74. package/src/storage/postgres-core.ts +0 -550
  75. package/src/storage/postgres-migration.test.ts +0 -69
  76. package/src/storage/postgres-transaction-pagination.test.ts +0 -107
  77. package/src/storage/postgres.ts +0 -835
  78. package/src/storage/sqlite-core.ts +0 -151
  79. package/src/storage/sqlite.test.ts +0 -642
  80. package/src/storage/sqlite.ts +0 -717
  81. package/src/storage/types.ts +0 -343
  82. package/src/utils/errors.test.ts +0 -150
  83. package/src/utils/errors.ts +0 -161
  84. package/src/utils/id.ts +0 -21
  85. package/src/utils/index.ts +0 -10
  86. package/src/utils/logger.test.ts +0 -121
  87. package/src/utils/logger.ts +0 -96
  88. package/src/utils/pinned-fetch.test.ts +0 -84
  89. package/src/utils/pinned-fetch.ts +0 -118
  90. package/src/utils/postgres-deps.ts +0 -79
  91. package/src/utils/retry.test.ts +0 -177
  92. package/src/utils/retry.ts +0 -121
  93. package/src/utils/safe-json.test.ts +0 -166
  94. package/src/utils/safe-json.ts +0 -107
  95. package/src/utils/webhook-signature.test.ts +0 -111
  96. package/src/utils/webhook-signature.ts +0 -74
  97. package/tsconfig.json +0 -21
  98. package/tsup.config.ts +0 -14
  99. package/vitest.config.ts +0 -9
@@ -1,165 +0,0 @@
1
- /**
2
- * Orchestrator persistence layer.
3
- *
4
- * Wraps the storage adapter and event transport with intent-named helpers
5
- * so the orchestrator and step-runner can stay focused on control flow
6
- * instead of repeating field shapes for every status transition.
7
- *
8
- * All side-effect calls the orchestrator makes against storage or the event
9
- * transport flow through this module — keeping the rest of the pipeline
10
- * pure(-ish) and easy to read.
11
- */
12
- import type { StorageAdapter, WorkflowRunStepRecord } from '../../storage/types';
13
- import type { EventTransport, WorkflowEvent } from '../../events/types';
14
- import type { Logger, WorkflowError } from '../types';
15
- import { WorkflowEngineError } from '../../utils/errors';
16
-
17
- /**
18
- * Mark the run as running. On a fresh start, also stamp `startedAt`;
19
- * on resume, leave the original `startedAt` alone.
20
- */
21
- export async function markRunRunning(
22
- storage: StorageAdapter,
23
- runId: string,
24
- isResume: boolean
25
- ): Promise<void> {
26
- await storage.updateRun(runId, {
27
- status: 'running',
28
- ...(isResume ? {} : { startedAt: new Date() }),
29
- });
30
- }
31
-
32
- /**
33
- * Persist the workflow checkpoint after a step completes:
34
- * the accumulated step results plus the list of completed step keys.
35
- */
36
- export async function saveCheckpoint(
37
- storage: StorageAdapter,
38
- runId: string,
39
- results: Record<string, unknown>,
40
- completedSteps: string[]
41
- ): Promise<void> {
42
- await storage.updateRun(runId, {
43
- context: { ...results },
44
- completedSteps: [...completedSteps],
45
- });
46
- }
47
-
48
- /**
49
- * Mark the run as succeeded, persisting results and finishedAt.
50
- */
51
- export async function markRunSucceeded(
52
- storage: StorageAdapter,
53
- runId: string,
54
- results: Record<string, unknown>
55
- ): Promise<void> {
56
- await storage.updateRun(runId, {
57
- status: 'succeeded',
58
- context: results,
59
- finishedAt: new Date(),
60
- });
61
- }
62
-
63
- /**
64
- * Persist a non-success terminal state for the run (failed / canceled / timeout).
65
- * `status` is the storage-level status — the orchestrator decides this from the
66
- * raised error using `state-transitions.ts`.
67
- */
68
- export async function markRunFinal(
69
- storage: StorageAdapter,
70
- runId: string,
71
- status: 'failed' | 'canceled',
72
- results: Record<string, unknown>,
73
- error: WorkflowError
74
- ): Promise<void> {
75
- await storage.updateRun(runId, {
76
- status,
77
- context: results,
78
- error,
79
- finishedAt: new Date(),
80
- });
81
- }
82
-
83
- /**
84
- * Create a fresh step record in 'running' state for the given attempt.
85
- * Returns the persisted record so the caller can read its generated id.
86
- */
87
- export async function createStepRecord(
88
- storage: StorageAdapter,
89
- runId: string,
90
- stepKey: string,
91
- stepName: string,
92
- attempt: number
93
- ): Promise<WorkflowRunStepRecord> {
94
- return storage.createStep({
95
- runId,
96
- stepKey,
97
- stepName,
98
- status: 'running',
99
- attempt,
100
- startedAt: new Date(),
101
- });
102
- }
103
-
104
- /**
105
- * Mark a step as succeeded, recording its result and finishedAt.
106
- */
107
- export async function markStepSucceeded(
108
- storage: StorageAdapter,
109
- stepId: string,
110
- result: unknown
111
- ): Promise<void> {
112
- await storage.updateStep(stepId, {
113
- status: 'succeeded',
114
- result,
115
- finishedAt: new Date(),
116
- });
117
- }
118
-
119
- /**
120
- * Mark a step as failed (or canceled, if the abort signal already fired)
121
- * with the normalized engine error attached.
122
- */
123
- export async function markStepFailed(
124
- storage: StorageAdapter,
125
- stepId: string,
126
- error: unknown,
127
- isCanceled: boolean
128
- ): Promise<void> {
129
- await storage.updateStep(stepId, {
130
- status: isCanceled ? 'canceled' : 'failed',
131
- error: WorkflowEngineError.fromError(error),
132
- finishedAt: new Date(),
133
- });
134
- }
135
-
136
- /**
137
- * Mark a step as canceled without an error payload — used when an in-flight
138
- * retry sleep is interrupted by an abort signal.
139
- */
140
- export async function markStepCanceled(
141
- storage: StorageAdapter,
142
- stepId: string
143
- ): Promise<void> {
144
- await storage.updateStep(stepId, {
145
- status: 'canceled',
146
- finishedAt: new Date(),
147
- });
148
- }
149
-
150
- /**
151
- * Emit a workflow event without ever throwing back into the caller.
152
- * Event emission failures are logged but never abort the workflow,
153
- * so a flaky transport can't take down a run.
154
- */
155
- export function safeEmitEvent(
156
- events: EventTransport,
157
- logger: Logger,
158
- event: WorkflowEvent
159
- ): void {
160
- try {
161
- events.emit(event);
162
- } catch (error) {
163
- logger.error('Failed to emit event:', error);
164
- }
165
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Plan resolution — turns ExecuteOptions into the runtime artefacts
3
- * the orchestrator needs to start running steps.
4
- *
5
- * Today the engine has no separate planner module: the "plan" is just the
6
- * declarative WorkflowDefinition combined with the optional resume
7
- * checkpoint and the supplied input/metadata. This module is the seam
8
- * where, in the future, recipe/plan resolution and input validation can
9
- * grow without bloating execute-workflow.ts.
10
- *
11
- * Responsibilities:
12
- * 1. Build the WorkflowContext from the run options + checkpoint, with a
13
- * `context.emit` helper that routes through safeEmitEvent.
14
- * 2. Set up the workflow-level timeout: returns a handle the caller
15
- * clears in both the success and failure paths, and a getter for
16
- * the WorkflowTimeoutError that fired (so the catch block can
17
- * surface it instead of the abort-derived error).
18
- * 3. Compute the initial set of completed-step keys (used by the loop
19
- * to skip steps already done in a previous run).
20
- */
21
- import type {
22
- WorkflowDefinition,
23
- WorkflowContext,
24
- Logger,
25
- SpawnChildOptions,
26
- } from '../types';
27
- import type { EventTransport } from '../../events/types';
28
- import { WorkflowTimeoutError } from '../../utils/errors';
29
- import { createScopedLogger } from '../../utils/logger';
30
- import { safeEmitEvent } from './persistence';
31
-
32
- /**
33
- * Inputs needed to resolve a workflow plan + build its execution context.
34
- */
35
- export interface BuildContextOptions {
36
- runId: string;
37
- definition: WorkflowDefinition;
38
- input: Record<string, unknown>;
39
- metadata: Record<string, unknown>;
40
- events: EventTransport;
41
- logger: Logger;
42
- abortController: AbortController;
43
- spawnChild: (options: SpawnChildOptions) => Promise<string>;
44
- /** Existing checkpoint results to preload into context.results. */
45
- checkpointResults?: Record<string, unknown>;
46
- }
47
-
48
- /**
49
- * Build the WorkflowContext that will be threaded through every step
50
- * handler. On resume, preload `context.results` from the checkpoint.
51
- */
52
- export function buildContext(options: BuildContextOptions): WorkflowContext {
53
- const {
54
- runId,
55
- definition,
56
- input,
57
- metadata,
58
- events,
59
- logger,
60
- abortController,
61
- spawnChild,
62
- checkpointResults,
63
- } = options;
64
-
65
- return {
66
- runId,
67
- stepId: '', // set per-step by the step-runner before each handler call
68
- kind: definition.kind,
69
- input,
70
- results: checkpointResults ? { ...checkpointResults } : {},
71
- metadata,
72
- logger: createScopedLogger(logger, runId),
73
- signal: abortController.signal,
74
- spawnChild,
75
- emit: (eventType: string, payload?: unknown) => {
76
- safeEmitEvent(events, logger, {
77
- runId,
78
- kind: definition.kind,
79
- eventType,
80
- timestamp: new Date(),
81
- payload,
82
- });
83
- },
84
- };
85
- }
86
-
87
- /**
88
- * Handle returned by setupWorkflowTimeout. The orchestrator must call
89
- * `clear()` on both success and failure paths to release the timer, and
90
- * may read `getTimeoutError()` in the catch path: if non-null, the abort
91
- * was triggered by our timeout (not user cancellation), and the caller
92
- * should surface that error instead of the generic abort-derived one.
93
- */
94
- export interface WorkflowTimeoutHandle {
95
- clear: () => void;
96
- getTimeoutError: () => WorkflowTimeoutError | undefined;
97
- }
98
-
99
- /**
100
- * Arm a workflow-level timeout if the definition specifies one.
101
- *
102
- * On fire: constructs a WorkflowTimeoutError (recoverable via
103
- * `getTimeoutError`) and aborts the controller, which causes any
104
- * in-flight step handler racing the abort signal to reject.
105
- *
106
- * If the definition has no timeout, returns no-op handles.
107
- */
108
- export function setupWorkflowTimeout(
109
- runId: string,
110
- definition: WorkflowDefinition,
111
- abortController: AbortController
112
- ): WorkflowTimeoutHandle {
113
- if (!definition.timeout) {
114
- return {
115
- clear: () => {},
116
- getTimeoutError: () => undefined,
117
- };
118
- }
119
-
120
- let timeoutError: WorkflowTimeoutError | undefined;
121
- const timeoutId = setTimeout(() => {
122
- timeoutError = new WorkflowTimeoutError(runId, definition.timeout!);
123
- abortController.abort();
124
- }, definition.timeout);
125
-
126
- return {
127
- clear: () => clearTimeout(timeoutId),
128
- getTimeoutError: () => timeoutError,
129
- };
130
- }
131
-
132
- /**
133
- * Initial list of step keys to consider already complete — empty for a
134
- * fresh run, the checkpoint's set for a resume.
135
- */
136
- export function initialCompletedSteps(
137
- checkpoint?: { completedStepKeys: Set<string> }
138
- ): string[] {
139
- return checkpoint ? Array.from(checkpoint.completedStepKeys) : [];
140
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * Pure state-transition decisions for the orchestrator.
3
- *
4
- * Every function in here is deterministic and side-effect-free: given
5
- * the current shape of an event, error, or step config they return what
6
- * the next state should be. The orchestrator and step-runner read these
7
- * decisions and apply them via persistence helpers.
8
- *
9
- * State-transition rules captured here:
10
- *
11
- * Run terminal status:
12
- * - WorkflowTimeoutError → status='failed', eventType='run.timeout'
13
- * - WorkflowCanceledError → status='canceled', eventType='run.canceled'
14
- * - any other error → status='failed', eventType='run.failed'
15
- *
16
- * Step error policy resolution (precedence):
17
- * - step.onError > definition.defaultOnError > 'fail'
18
- * - step.maxRetries > 3
19
- * - step.retryDelay (ms) > 1000
20
- * - step.retryBackoff (multiplier) > 2
21
- *
22
- * Step retry decision (after a non-cancellation failure):
23
- * - onError === 'skip' → 'skip'
24
- * - onError === 'retry' && attempt <= maxRetries → 'retry'
25
- * - otherwise (incl. retries exhausted, 'fail') → 'fail'
26
- */
27
- import type { WorkflowDefinition, WorkflowStep } from '../types';
28
- import { WorkflowCanceledError, WorkflowTimeoutError } from '../../utils/errors';
29
-
30
- /**
31
- * Storage-level statuses the orchestrator may persist for a non-success run.
32
- */
33
- export type RunFinalStatus = 'failed' | 'canceled';
34
-
35
- /**
36
- * Event types emitted on a non-success run completion.
37
- */
38
- export type RunFinalEventType = 'run.timeout' | 'run.canceled' | 'run.failed';
39
-
40
- /**
41
- * Result of mapping an error to a terminal run state.
42
- */
43
- export interface RunFinalDecision {
44
- status: RunFinalStatus;
45
- eventType: RunFinalEventType;
46
- }
47
-
48
- /**
49
- * Map a thrown workflow error to the final run status + event type.
50
- * Note: a workflow timeout is persisted as `failed` for storage compatibility,
51
- * but the emitted event distinguishes it via `run.timeout`.
52
- */
53
- export function decideRunFinalState(error: unknown): RunFinalDecision {
54
- if (error instanceof WorkflowTimeoutError) {
55
- return { status: 'failed', eventType: 'run.timeout' };
56
- }
57
- if (error instanceof WorkflowCanceledError) {
58
- return { status: 'canceled', eventType: 'run.canceled' };
59
- }
60
- return { status: 'failed', eventType: 'run.failed' };
61
- }
62
-
63
- /**
64
- * Step-level error policy resolved from a step + workflow definition.
65
- */
66
- export interface StepErrorPolicy {
67
- onError: 'fail' | 'skip' | 'retry';
68
- maxRetries: number;
69
- retryDelay: number;
70
- retryBackoff: number;
71
- }
72
-
73
- /**
74
- * Resolve the effective error policy for a step.
75
- * Step-level config overrides workflow defaults; workflow defaults override
76
- * the engine defaults (`fail` / 3 / 1000ms / 2x).
77
- */
78
- export function resolveStepErrorPolicy<TInput>(
79
- step: WorkflowStep<TInput>,
80
- definition: WorkflowDefinition<TInput>
81
- ): StepErrorPolicy {
82
- return {
83
- onError: step.onError ?? definition.defaultOnError ?? 'fail',
84
- maxRetries: step.maxRetries ?? 3,
85
- retryDelay: step.retryDelay ?? 1000,
86
- retryBackoff: step.retryBackoff ?? 2,
87
- };
88
- }
89
-
90
- /**
91
- * Decision returned after a step attempt fails.
92
- *
93
- * - 'skip' : swallow the error and treat the step as a no-op (returns undefined)
94
- * - 'retry' : back off and try again
95
- * - 'fail' : escalate (StepError) and abort the workflow
96
- */
97
- export type StepFailureAction = 'skip' | 'retry' | 'fail';
98
-
99
- /**
100
- * Decide what to do after a step attempt has failed (and was not canceled).
101
- */
102
- export function decideStepFailureAction(
103
- policy: StepErrorPolicy,
104
- attempt: number
105
- ): StepFailureAction {
106
- if (policy.onError === 'skip') return 'skip';
107
- if (policy.onError === 'retry' && attempt <= policy.maxRetries) return 'retry';
108
- return 'fail';
109
- }