@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.
- package/README.md +9 -0
- package/dist/{chunk-U3PCVK6O.js → chunk-D5HW7VEG.js} +4 -2
- package/dist/chunk-D5HW7VEG.js.map +1 -0
- package/dist/{chunk-5XJBAPXF.js → chunk-JUPZEBR3.js} +3 -1
- package/dist/{chunk-5XJBAPXF.js.map → chunk-JUPZEBR3.js.map} +1 -1
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.js +1 -1
- package/dist/index.js +2 -2
- package/dist/storage/index.js +1 -1
- package/package.json +23 -12
- package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
- package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
- package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
- package/.github/CODEOWNERS +0 -15
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/publish.yml +0 -42
- package/.github/workflows/security.yml +0 -29
- package/CHANGELOG.md +0 -34
- package/SECURITY.md +0 -40
- package/dist/chunk-U3PCVK6O.js.map +0 -1
- package/docs/github-packages-setup.md +0 -97
- package/docs/stepflow-api-reference.md +0 -1663
- package/src/core/engine/concurrency.ts +0 -229
- package/src/core/engine/lifecycle.ts +0 -176
- package/src/core/engine.test.ts +0 -1435
- package/src/core/engine.ts +0 -598
- package/src/core/index.ts +0 -7
- package/src/core/orchestrator/persistence.ts +0 -165
- package/src/core/orchestrator/plan-resolution.ts +0 -140
- package/src/core/orchestrator/state-transitions.ts +0 -109
- package/src/core/orchestrator/step-runner.ts +0 -427
- package/src/core/orchestrator.ts +0 -252
- package/src/core/types.ts +0 -220
- package/src/events/index.ts +0 -8
- package/src/events/memory.test.ts +0 -107
- package/src/events/memory.ts +0 -92
- package/src/events/socketio.test.ts +0 -378
- package/src/events/socketio.ts +0 -262
- package/src/events/types.ts +0 -118
- package/src/events/webhook.test.ts +0 -949
- package/src/events/webhook.ts +0 -807
- package/src/index.ts +0 -270
- package/src/planning/evaluate-condition.test.ts +0 -153
- package/src/planning/evaluate-condition.ts +0 -105
- package/src/planning/index.ts +0 -55
- package/src/planning/planner.ts +0 -494
- package/src/planning/planning.test.ts +0 -727
- package/src/planning/registry.test.ts +0 -398
- package/src/planning/registry.ts +0 -283
- package/src/planning/types.ts +0 -566
- package/src/scheduler/cron.test.ts +0 -479
- package/src/scheduler/cron.ts +0 -438
- package/src/scheduler/index.ts +0 -8
- package/src/scheduler/postgres-persistence.ts +0 -530
- package/src/scheduler/sqlite-persistence.test.ts +0 -235
- package/src/scheduler/sqlite-persistence.ts +0 -286
- package/src/scheduler/types.ts +0 -73
- package/src/storage/index.ts +0 -27
- package/src/storage/memory.test.ts +0 -349
- package/src/storage/memory.ts +0 -227
- package/src/storage/postgres-core.ts +0 -550
- package/src/storage/postgres-migration.test.ts +0 -69
- package/src/storage/postgres-transaction-pagination.test.ts +0 -107
- package/src/storage/postgres.ts +0 -835
- package/src/storage/sqlite-core.ts +0 -151
- package/src/storage/sqlite.test.ts +0 -642
- package/src/storage/sqlite.ts +0 -717
- package/src/storage/types.ts +0 -343
- package/src/utils/errors.test.ts +0 -150
- package/src/utils/errors.ts +0 -161
- package/src/utils/id.ts +0 -21
- package/src/utils/index.ts +0 -10
- package/src/utils/logger.test.ts +0 -121
- package/src/utils/logger.ts +0 -96
- package/src/utils/pinned-fetch.test.ts +0 -84
- package/src/utils/pinned-fetch.ts +0 -118
- package/src/utils/postgres-deps.ts +0 -79
- package/src/utils/retry.test.ts +0 -177
- package/src/utils/retry.ts +0 -121
- package/src/utils/safe-json.test.ts +0 -166
- package/src/utils/safe-json.ts +0 -107
- package/src/utils/webhook-signature.test.ts +0 -111
- package/src/utils/webhook-signature.ts +0 -74
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -14
- 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
|
-
}
|