@workflow/core 4.0.1-beta.8 → 4.1.0-beta.51
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/dist/builtins.js +1 -1
- package/dist/class-serialization.d.ts +26 -0
- package/dist/class-serialization.d.ts.map +1 -0
- package/dist/class-serialization.js +66 -0
- package/dist/create-hook.js +1 -1
- package/dist/define-hook.d.ts +40 -25
- package/dist/define-hook.d.ts.map +1 -1
- package/dist/define-hook.js +22 -27
- package/dist/events-consumer.d.ts.map +1 -1
- package/dist/events-consumer.js +5 -1
- package/dist/flushable-stream.d.ts +82 -0
- package/dist/flushable-stream.d.ts.map +1 -0
- package/dist/flushable-stream.js +214 -0
- package/dist/global.d.ts +4 -1
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +21 -9
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/logger.js +1 -1
- package/dist/observability.d.ts +60 -0
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +265 -32
- package/dist/private.d.ts +10 -1
- package/dist/private.d.ts.map +1 -1
- package/dist/private.js +6 -1
- package/dist/runtime/helpers.d.ts +52 -0
- package/dist/runtime/helpers.d.ts.map +1 -0
- package/dist/runtime/helpers.js +264 -0
- package/dist/runtime/resume-hook.d.ts +17 -12
- package/dist/runtime/resume-hook.d.ts.map +1 -1
- package/dist/runtime/resume-hook.js +79 -64
- package/dist/runtime/start.d.ts +14 -0
- package/dist/runtime/start.d.ts.map +1 -1
- package/dist/runtime/start.js +71 -45
- package/dist/runtime/step-handler.d.ts +7 -0
- package/dist/runtime/step-handler.d.ts.map +1 -0
- package/dist/runtime/step-handler.js +337 -0
- package/dist/runtime/suspension-handler.d.ts +25 -0
- package/dist/runtime/suspension-handler.d.ts.map +1 -0
- package/dist/runtime/suspension-handler.js +182 -0
- package/dist/runtime/world.d.ts.map +1 -1
- package/dist/runtime/world.js +20 -21
- package/dist/runtime.d.ts +3 -7
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +103 -410
- package/dist/schemas.d.ts +1 -15
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2 -15
- package/dist/serialization.d.ts +112 -21
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +469 -85
- package/dist/sleep.d.ts +10 -0
- package/dist/sleep.d.ts.map +1 -1
- package/dist/sleep.js +1 -1
- package/dist/source-map.d.ts +10 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +56 -0
- package/dist/step/context-storage.d.ts +2 -0
- package/dist/step/context-storage.d.ts.map +1 -1
- package/dist/step/context-storage.js +1 -1
- package/dist/step/get-closure-vars.d.ts +9 -0
- package/dist/step/get-closure-vars.d.ts.map +1 -0
- package/dist/step/get-closure-vars.js +16 -0
- package/dist/step/get-step-metadata.js +1 -1
- package/dist/step/get-workflow-metadata.js +1 -1
- package/dist/{writable-stream.d.ts → step/writable-stream.d.ts} +5 -5
- package/dist/step/writable-stream.d.ts.map +1 -0
- package/dist/step/writable-stream.js +30 -0
- package/dist/step.d.ts +1 -1
- package/dist/step.d.ts.map +1 -1
- package/dist/step.js +93 -47
- package/dist/symbols.d.ts +6 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +7 -1
- package/dist/telemetry/semantic-conventions.d.ts +66 -38
- package/dist/telemetry/semantic-conventions.d.ts.map +1 -1
- package/dist/telemetry/semantic-conventions.js +16 -3
- package/dist/telemetry.d.ts +8 -4
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +39 -6
- package/dist/types.js +1 -1
- package/dist/util.d.ts +5 -24
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -38
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/vm/index.js +2 -2
- package/dist/vm/uuid.js +1 -1
- package/dist/workflow/create-hook.js +1 -1
- package/dist/workflow/define-hook.d.ts +3 -3
- package/dist/workflow/define-hook.d.ts.map +1 -1
- package/dist/workflow/define-hook.js +1 -1
- package/dist/workflow/get-workflow-metadata.js +1 -1
- package/dist/workflow/hook.d.ts.map +1 -1
- package/dist/workflow/hook.js +49 -14
- package/dist/workflow/index.d.ts +1 -1
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +2 -2
- package/dist/workflow/sleep.d.ts +1 -1
- package/dist/workflow/sleep.d.ts.map +1 -1
- package/dist/workflow/sleep.js +26 -39
- package/dist/workflow/writable-stream.d.ts +1 -1
- package/dist/workflow/writable-stream.d.ts.map +1 -1
- package/dist/workflow/writable-stream.js +1 -1
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +72 -9
- package/docs/api-reference/create-hook.mdx +133 -0
- package/docs/api-reference/create-webhook.mdx +225 -0
- package/docs/api-reference/define-hook.mdx +206 -0
- package/docs/api-reference/fatal-error.mdx +37 -0
- package/docs/api-reference/fetch.mdx +139 -0
- package/docs/api-reference/get-step-metadata.mdx +76 -0
- package/docs/api-reference/get-workflow-metadata.mdx +44 -0
- package/docs/api-reference/get-writable.mdx +292 -0
- package/docs/api-reference/index.mdx +55 -0
- package/docs/api-reference/meta.json +3 -0
- package/docs/api-reference/retryable-error.mdx +106 -0
- package/docs/api-reference/sleep.mdx +59 -0
- package/docs/foundations/common-patterns.mdx +253 -0
- package/docs/foundations/errors-and-retries.mdx +190 -0
- package/docs/foundations/hooks.mdx +455 -0
- package/docs/foundations/idempotency.mdx +55 -0
- package/docs/foundations/index.mdx +32 -0
- package/docs/foundations/meta.json +14 -0
- package/docs/foundations/serialization.mdx +157 -0
- package/docs/foundations/starting-workflows.mdx +211 -0
- package/docs/foundations/streaming.mdx +569 -0
- package/docs/foundations/workflows-and-steps.mdx +197 -0
- package/docs/how-it-works/code-transform.mdx +334 -0
- package/docs/how-it-works/event-sourcing.mdx +254 -0
- package/docs/how-it-works/framework-integrations.mdx +437 -0
- package/docs/how-it-works/meta.json +10 -0
- package/docs/how-it-works/understanding-directives.mdx +611 -0
- package/package.json +31 -25
- package/dist/builtins.js.map +0 -1
- package/dist/create-hook.js.map +0 -1
- package/dist/define-hook.js.map +0 -1
- package/dist/events-consumer.js.map +0 -1
- package/dist/global.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/observability.js.map +0 -1
- package/dist/parse-name.d.ts +0 -25
- package/dist/parse-name.d.ts.map +0 -1
- package/dist/parse-name.js +0 -40
- package/dist/parse-name.js.map +0 -1
- package/dist/private.js.map +0 -1
- package/dist/runtime/resume-hook.js.map +0 -1
- package/dist/runtime/start.js.map +0 -1
- package/dist/runtime/world.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/serialization.js.map +0 -1
- package/dist/sleep.js.map +0 -1
- package/dist/step/context-storage.js.map +0 -1
- package/dist/step/get-step-metadata.js.map +0 -1
- package/dist/step/get-workflow-metadata.js.map +0 -1
- package/dist/step.js.map +0 -1
- package/dist/symbols.js.map +0 -1
- package/dist/telemetry/semantic-conventions.js.map +0 -1
- package/dist/telemetry.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/util.js.map +0 -1
- package/dist/vm/index.js.map +0 -1
- package/dist/vm/uuid.js.map +0 -1
- package/dist/workflow/create-hook.js.map +0 -1
- package/dist/workflow/define-hook.js.map +0 -1
- package/dist/workflow/get-workflow-metadata.js.map +0 -1
- package/dist/workflow/hook.js.map +0 -1
- package/dist/workflow/index.js.map +0 -1
- package/dist/workflow/sleep.js.map +0 -1
- package/dist/workflow/writable-stream.js.map +0 -1
- package/dist/workflow.js.map +0 -1
- package/dist/writable-stream.d.ts.map +0 -1
- package/dist/writable-stream.js +0 -16
- package/dist/writable-stream.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRuntimeError, } from '@workflow/errors';
|
|
2
|
+
import { parseWorkflowName } from '@workflow/utils/parse-name';
|
|
3
|
+
import { SPEC_VERSION_CURRENT, WorkflowInvokePayloadSchema, } from '@workflow/world';
|
|
3
4
|
import { WorkflowSuspension } from './global.js';
|
|
4
5
|
import { runtimeLogger } from './logger.js';
|
|
5
|
-
import {
|
|
6
|
+
import { getAllWorkflowRunEvents, getQueueOverhead, handleHealthCheckMessage, parseHealthCheckPayload, withHealthCheck, } from './runtime/helpers.js';
|
|
7
|
+
import { handleSuspension } from './runtime/suspension-handler.js';
|
|
6
8
|
import { getWorld, getWorldHandlers } from './runtime/world.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
// TODO: move step handler out to a separate file
|
|
10
|
-
import { contextStorage } from './step/context-storage.js';
|
|
9
|
+
import { getExternalRevivers, hydrateWorkflowReturnValue, } from './serialization.js';
|
|
10
|
+
import { remapErrorStack } from './source-map.js';
|
|
11
11
|
import * as Attribute from './telemetry/semantic-conventions.js';
|
|
12
|
-
import {
|
|
12
|
+
import { linkToCurrentContext, trace, withTraceContext } from './telemetry.js';
|
|
13
13
|
import { getErrorName, getErrorStack } from './types.js';
|
|
14
14
|
import { buildWorkflowSuspensionMessage, getWorkflowRunStreamId, } from './util.js';
|
|
15
15
|
import { runWorkflow } from './workflow.js';
|
|
16
16
|
export { WorkflowSuspension } from './global.js';
|
|
17
|
+
export { healthCheck, } from './runtime/helpers.js';
|
|
17
18
|
export { getHookByToken, resumeHook, resumeWebhook, } from './runtime/resume-hook.js';
|
|
18
19
|
export { start } from './runtime/start.js';
|
|
20
|
+
export { stepEntrypoint } from './runtime/step-handler.js';
|
|
19
21
|
export { createWorld, getWorld, getWorldHandlers, setWorld, } from './runtime/world.js';
|
|
20
22
|
/**
|
|
21
23
|
* A handler class for a workflow run.
|
|
@@ -38,7 +40,10 @@ export class Run {
|
|
|
38
40
|
* Cancels the workflow run.
|
|
39
41
|
*/
|
|
40
42
|
async cancel() {
|
|
41
|
-
await this.world.
|
|
43
|
+
await this.world.events.create(this.runId, {
|
|
44
|
+
eventType: 'run_cancelled',
|
|
45
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
46
|
+
});
|
|
42
47
|
}
|
|
43
48
|
/**
|
|
44
49
|
* The status of the workflow run.
|
|
@@ -95,7 +100,7 @@ export class Run {
|
|
|
95
100
|
getReadable(options = {}) {
|
|
96
101
|
const { ops = [], global = globalThis, startIndex, namespace } = options;
|
|
97
102
|
const name = getWorkflowRunStreamId(this.runId, namespace);
|
|
98
|
-
return getExternalRevivers(global, ops).ReadableStream({
|
|
103
|
+
return getExternalRevivers(global, ops, this.runId).ReadableStream({
|
|
99
104
|
name,
|
|
100
105
|
startIndex,
|
|
101
106
|
});
|
|
@@ -110,13 +115,13 @@ export class Run {
|
|
|
110
115
|
try {
|
|
111
116
|
const run = await this.world.runs.get(this.runId);
|
|
112
117
|
if (run.status === 'completed') {
|
|
113
|
-
return hydrateWorkflowReturnValue(run.output, [],
|
|
118
|
+
return hydrateWorkflowReturnValue(run.output, [], this.runId);
|
|
114
119
|
}
|
|
115
120
|
if (run.status === 'cancelled') {
|
|
116
121
|
throw new WorkflowRunCancelledError(this.runId);
|
|
117
122
|
}
|
|
118
123
|
if (run.status === 'failed') {
|
|
119
|
-
throw new WorkflowRunFailedError(this.runId, run.error
|
|
124
|
+
throw new WorkflowRunFailedError(this.runId, run.error);
|
|
120
125
|
}
|
|
121
126
|
throw new WorkflowRunNotCompletedError(this.runId, run.status);
|
|
122
127
|
}
|
|
@@ -140,30 +145,6 @@ export class Run {
|
|
|
140
145
|
export function getRun(runId) {
|
|
141
146
|
return new Run(runId);
|
|
142
147
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Loads all workflow run events by iterating through all pages of paginated results.
|
|
145
|
-
* This ensures that *all* events are loaded into memory before running the workflow.
|
|
146
|
-
* Events must be in chronological order (ascending) for proper workflow replay.
|
|
147
|
-
*/
|
|
148
|
-
async function getAllWorkflowRunEvents(runId) {
|
|
149
|
-
const allEvents = [];
|
|
150
|
-
let cursor = null;
|
|
151
|
-
let hasMore = true;
|
|
152
|
-
const world = getWorld();
|
|
153
|
-
while (hasMore) {
|
|
154
|
-
const response = await world.events.list({
|
|
155
|
-
runId,
|
|
156
|
-
pagination: {
|
|
157
|
-
sortOrder: 'asc', // Required: events must be in chronological order for replay
|
|
158
|
-
cursor: cursor ?? undefined,
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
allEvents.push(...response.data);
|
|
162
|
-
hasMore = response.hasMore;
|
|
163
|
-
cursor = response.cursor;
|
|
164
|
-
}
|
|
165
|
-
return allEvents;
|
|
166
|
-
}
|
|
167
148
|
/**
|
|
168
149
|
* Function that creates a single route which handles any workflow execution
|
|
169
150
|
* request and routes to the appropriate workflow function.
|
|
@@ -173,18 +154,30 @@ async function getAllWorkflowRunEvents(runId) {
|
|
|
173
154
|
* @returns A function that can be used as a Vercel API route.
|
|
174
155
|
*/
|
|
175
156
|
export function workflowEntrypoint(workflowCode) {
|
|
176
|
-
|
|
177
|
-
|
|
157
|
+
const handler = getWorldHandlers().createQueueHandler('__wkf_workflow_', async (message_, metadata) => {
|
|
158
|
+
// Check if this is a health check message
|
|
159
|
+
// NOTE: Health check messages are intentionally unauthenticated for monitoring purposes.
|
|
160
|
+
// They only write a simple status response to a stream and do not expose sensitive data.
|
|
161
|
+
// The stream name includes a unique correlationId that must be known by the caller.
|
|
162
|
+
const healthCheck = parseHealthCheckPayload(message_);
|
|
163
|
+
if (healthCheck) {
|
|
164
|
+
await handleHealthCheckMessage(healthCheck, 'workflow');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const { runId, traceCarrier: traceContext, requestedAt, } = WorkflowInvokePayloadSchema.parse(message_);
|
|
178
168
|
// Extract the workflow name from the topic name
|
|
179
169
|
const workflowName = metadata.queueName.slice('__wkf_workflow_'.length);
|
|
170
|
+
const spanLinks = await linkToCurrentContext();
|
|
180
171
|
// Invoke user workflow within the propagated trace context
|
|
181
172
|
return await withTraceContext(traceContext, async () => {
|
|
182
173
|
const world = getWorld();
|
|
183
|
-
return trace(`WORKFLOW ${workflowName}`, async (span) => {
|
|
174
|
+
return trace(`WORKFLOW ${workflowName}`, { links: spanLinks }, async (span) => {
|
|
184
175
|
span?.setAttributes({
|
|
185
176
|
...Attribute.WorkflowName(workflowName),
|
|
186
177
|
...Attribute.WorkflowOperation('execute'),
|
|
187
178
|
...Attribute.QueueName(metadata.queueName),
|
|
179
|
+
...Attribute.QueueMessageId(metadata.messageId),
|
|
180
|
+
...getQueueOverhead({ requestedAt }),
|
|
188
181
|
});
|
|
189
182
|
// TODO: validate `workflowName` exists before consuming message?
|
|
190
183
|
span?.setAttributes({
|
|
@@ -195,15 +188,21 @@ export function workflowEntrypoint(workflowCode) {
|
|
|
195
188
|
try {
|
|
196
189
|
let workflowRun = await world.runs.get(runId);
|
|
197
190
|
if (workflowRun.status === 'pending') {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
// Transition run to 'running' via event (event-sourced architecture)
|
|
192
|
+
const result = await world.events.create(runId, {
|
|
193
|
+
eventType: 'run_started',
|
|
194
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
201
195
|
});
|
|
196
|
+
// Use the run entity from the event response (no extra get call needed)
|
|
197
|
+
if (!result.run) {
|
|
198
|
+
throw new WorkflowRuntimeError(`Event creation for 'run_started' did not return the run entity for run \"${runId}\"`);
|
|
199
|
+
}
|
|
200
|
+
workflowRun = result.run;
|
|
202
201
|
}
|
|
203
202
|
// At this point, the workflow is "running" and `startedAt` should
|
|
204
203
|
// definitely be set.
|
|
205
204
|
if (!workflowRun.startedAt) {
|
|
206
|
-
throw new
|
|
205
|
+
throw new WorkflowRuntimeError(`Workflow run "${runId}" has no "startedAt" timestamp`);
|
|
207
206
|
}
|
|
208
207
|
workflowStartedAt = +workflowRun.startedAt;
|
|
209
208
|
span?.setAttributes({
|
|
@@ -224,27 +223,35 @@ export function workflowEntrypoint(workflowCode) {
|
|
|
224
223
|
const events = await getAllWorkflowRunEvents(workflowRun.runId);
|
|
225
224
|
// Check for any elapsed waits and create wait_completed events
|
|
226
225
|
const now = Date.now();
|
|
227
|
-
for (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
226
|
+
// Pre-compute completed correlation IDs for O(n) lookup instead of O(n²)
|
|
227
|
+
const completedWaitIds = new Set(events
|
|
228
|
+
.filter((e) => e.eventType === 'wait_completed')
|
|
229
|
+
.map((e) => e.correlationId));
|
|
230
|
+
// Collect all waits that need completion
|
|
231
|
+
const waitsToComplete = events
|
|
232
|
+
.filter((e) => e.eventType === 'wait_created' &&
|
|
233
|
+
e.correlationId !== undefined &&
|
|
234
|
+
!completedWaitIds.has(e.correlationId) &&
|
|
235
|
+
now >= e.eventData.resumeAt.getTime())
|
|
236
|
+
.map((e) => ({
|
|
237
|
+
eventType: 'wait_completed',
|
|
238
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
239
|
+
correlationId: e.correlationId,
|
|
240
|
+
}));
|
|
241
|
+
// Create all wait_completed events
|
|
242
|
+
for (const waitEvent of waitsToComplete) {
|
|
243
|
+
const result = await world.events.create(runId, waitEvent);
|
|
244
|
+
// Add the event to the events array so the workflow can see it
|
|
245
|
+
events.push(result.event);
|
|
242
246
|
}
|
|
243
247
|
const result = await runWorkflow(workflowCode, workflowRun, events);
|
|
244
|
-
//
|
|
245
|
-
await world.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
+
// Complete the workflow run via event (event-sourced architecture)
|
|
249
|
+
await world.events.create(runId, {
|
|
250
|
+
eventType: 'run_completed',
|
|
251
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
252
|
+
eventData: {
|
|
253
|
+
output: result,
|
|
254
|
+
},
|
|
248
255
|
});
|
|
249
256
|
span?.setAttributes({
|
|
250
257
|
...Attribute.WorkflowRunStatus('completed'),
|
|
@@ -255,128 +262,45 @@ export function workflowEntrypoint(workflowCode) {
|
|
|
255
262
|
if (WorkflowSuspension.is(err)) {
|
|
256
263
|
const suspensionMessage = buildWorkflowSuspensionMessage(runId, err.stepCount, err.hookCount, err.waitCount);
|
|
257
264
|
if (suspensionMessage) {
|
|
258
|
-
|
|
259
|
-
// console.debug(suspensionMessage);
|
|
260
|
-
}
|
|
261
|
-
// Process each operation in the queue (steps and hooks)
|
|
262
|
-
let minTimeoutSeconds = null;
|
|
263
|
-
for (const queueItem of err.steps) {
|
|
264
|
-
if (queueItem.type === 'step') {
|
|
265
|
-
// Handle step operations
|
|
266
|
-
const ops = [];
|
|
267
|
-
const dehydratedArgs = dehydrateStepArguments(queueItem.args, err.globalThis);
|
|
268
|
-
try {
|
|
269
|
-
const step = await world.steps.create(runId, {
|
|
270
|
-
stepId: queueItem.correlationId,
|
|
271
|
-
stepName: queueItem.stepName,
|
|
272
|
-
input: dehydratedArgs,
|
|
273
|
-
});
|
|
274
|
-
waitUntil(Promise.all(ops));
|
|
275
|
-
await world.queue(`__wkf_step_${queueItem.stepName}`, {
|
|
276
|
-
workflowName,
|
|
277
|
-
workflowRunId: runId,
|
|
278
|
-
workflowStartedAt,
|
|
279
|
-
stepId: step.stepId,
|
|
280
|
-
traceCarrier: await serializeTraceCarrier(),
|
|
281
|
-
}, {
|
|
282
|
-
idempotencyKey: queueItem.correlationId,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
catch (err) {
|
|
286
|
-
if (WorkflowAPIError.is(err) && err.status === 409) {
|
|
287
|
-
// Step already exists, so we can skip it
|
|
288
|
-
console.warn(`Step "${queueItem.stepName}" with correlation ID "${queueItem.correlationId}" already exists, skipping: ${err.message}`);
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
throw err;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (queueItem.type === 'hook') {
|
|
295
|
-
// Handle hook operations
|
|
296
|
-
try {
|
|
297
|
-
// Create hook in database
|
|
298
|
-
const hookMetadata = typeof queueItem.metadata === 'undefined'
|
|
299
|
-
? undefined
|
|
300
|
-
: dehydrateStepArguments(queueItem.metadata, err.globalThis);
|
|
301
|
-
await world.hooks.create(runId, {
|
|
302
|
-
hookId: queueItem.correlationId,
|
|
303
|
-
token: queueItem.token,
|
|
304
|
-
metadata: hookMetadata,
|
|
305
|
-
});
|
|
306
|
-
// Create hook_created event in event log
|
|
307
|
-
await world.events.create(runId, {
|
|
308
|
-
eventType: 'hook_created',
|
|
309
|
-
correlationId: queueItem.correlationId,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
catch (err) {
|
|
313
|
-
if (WorkflowAPIError.is(err)) {
|
|
314
|
-
if (err.status === 409) {
|
|
315
|
-
// Hook already exists (duplicate hook_id constraint), so we can skip it
|
|
316
|
-
console.warn(`Hook with correlation ID "${queueItem.correlationId}" already exists, skipping: ${err.message}`);
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
else if (err.status === 410) {
|
|
320
|
-
// Workflow has already completed, so no-op
|
|
321
|
-
console.warn(`Workflow run "${runId}" has already completed, skipping hook "${queueItem.correlationId}": ${err.message}`);
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
throw err;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
else if (queueItem.type === 'wait') {
|
|
329
|
-
// Handle wait operations
|
|
330
|
-
try {
|
|
331
|
-
// Only create wait_created event if it hasn't been created yet
|
|
332
|
-
if (!queueItem.hasCreatedEvent) {
|
|
333
|
-
await world.events.create(runId, {
|
|
334
|
-
eventType: 'wait_created',
|
|
335
|
-
correlationId: queueItem.correlationId,
|
|
336
|
-
eventData: {
|
|
337
|
-
resumeAt: queueItem.resumeAt,
|
|
338
|
-
},
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
// Calculate how long to wait before resuming
|
|
342
|
-
const now = Date.now();
|
|
343
|
-
const resumeAtMs = queueItem.resumeAt.getTime();
|
|
344
|
-
const delayMs = Math.max(1000, resumeAtMs - now);
|
|
345
|
-
const timeoutSeconds = Math.ceil(delayMs / 1000);
|
|
346
|
-
// Track the minimum timeout across all waits
|
|
347
|
-
if (minTimeoutSeconds === null ||
|
|
348
|
-
timeoutSeconds < minTimeoutSeconds) {
|
|
349
|
-
minTimeoutSeconds = timeoutSeconds;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch (err) {
|
|
353
|
-
if (WorkflowAPIError.is(err) && err.status === 409) {
|
|
354
|
-
// Wait already exists, so we can skip it
|
|
355
|
-
console.warn(`Wait with correlation ID "${queueItem.correlationId}" already exists, skipping: ${err.message}`);
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
throw err;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
265
|
+
runtimeLogger.debug(suspensionMessage);
|
|
361
266
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
267
|
+
const result = await handleSuspension({
|
|
268
|
+
suspension: err,
|
|
269
|
+
world,
|
|
270
|
+
runId,
|
|
271
|
+
workflowName,
|
|
272
|
+
workflowStartedAt,
|
|
273
|
+
span,
|
|
365
274
|
});
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return { timeoutSeconds: minTimeoutSeconds };
|
|
275
|
+
if (result.timeoutSeconds !== undefined) {
|
|
276
|
+
return { timeoutSeconds: result.timeoutSeconds };
|
|
369
277
|
}
|
|
370
278
|
}
|
|
371
279
|
else {
|
|
280
|
+
// NOTE: this error could be an error thrown in user code, or could also be a WorkflowRuntimeError
|
|
281
|
+
// (for instance when the event log is corrupted, this is thrown by the event consumer). We could
|
|
282
|
+
// specially handle these if needed.
|
|
372
283
|
const errorName = getErrorName(err);
|
|
373
|
-
const
|
|
284
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
285
|
+
let errorStack = getErrorStack(err);
|
|
286
|
+
// Remap error stack using source maps to show original source locations
|
|
287
|
+
if (errorStack) {
|
|
288
|
+
const parsedName = parseWorkflowName(workflowName);
|
|
289
|
+
const filename = parsedName?.path || workflowName;
|
|
290
|
+
errorStack = remapErrorStack(errorStack, filename, workflowCode);
|
|
291
|
+
}
|
|
374
292
|
console.error(`${errorName} while running "${runId}" workflow:\n\n${errorStack}`);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
293
|
+
// Fail the workflow run via event (event-sourced architecture)
|
|
294
|
+
await world.events.create(runId, {
|
|
295
|
+
eventType: 'run_failed',
|
|
296
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
297
|
+
eventData: {
|
|
298
|
+
error: {
|
|
299
|
+
message: errorMessage,
|
|
300
|
+
stack: errorStack,
|
|
301
|
+
},
|
|
302
|
+
// TODO: include error codes when we define them
|
|
303
|
+
},
|
|
380
304
|
});
|
|
381
305
|
span?.setAttributes({
|
|
382
306
|
...Attribute.WorkflowRunStatus('failed'),
|
|
@@ -388,240 +312,9 @@ export function workflowEntrypoint(workflowCode) {
|
|
|
388
312
|
}); // End withTraceContext
|
|
389
313
|
});
|
|
390
314
|
});
|
|
315
|
+
return withHealthCheck(handler);
|
|
391
316
|
}
|
|
392
|
-
/**
|
|
393
|
-
* A single route that handles any step execution request and routes to the
|
|
394
|
-
* appropriate step function. We may eventually want to create different bundles
|
|
395
|
-
* for each step, this is temporary.
|
|
396
|
-
*/
|
|
397
|
-
export const stepEntrypoint =
|
|
398
|
-
/* @__PURE__ */ getWorldHandlers().createQueueHandler('__wkf_step_', async (message_, metadata) => {
|
|
399
|
-
const { workflowName, workflowRunId, workflowStartedAt, stepId, traceCarrier: traceContext, } = StepInvokePayloadSchema.parse(message_);
|
|
400
|
-
// Execute step within the propagated trace context
|
|
401
|
-
return await withTraceContext(traceContext, async () => {
|
|
402
|
-
// Extract the step name from the topic name
|
|
403
|
-
const stepName = metadata.queueName.slice('__wkf_step_'.length);
|
|
404
|
-
const world = getWorld();
|
|
405
|
-
return trace(`STEP ${stepName}`, async (span) => {
|
|
406
|
-
span?.setAttributes({
|
|
407
|
-
...Attribute.StepName(stepName),
|
|
408
|
-
...Attribute.StepAttempt(metadata.attempt),
|
|
409
|
-
...Attribute.QueueName(metadata.queueName),
|
|
410
|
-
});
|
|
411
|
-
const stepFn = getStepFunction(stepName);
|
|
412
|
-
if (!stepFn) {
|
|
413
|
-
throw new Error(`Step "${stepName}" not found`);
|
|
414
|
-
}
|
|
415
|
-
if (typeof stepFn !== 'function') {
|
|
416
|
-
throw new Error(`Step "${stepName}" is not a function (got ${typeof stepFn})`);
|
|
417
|
-
}
|
|
418
|
-
span?.setAttributes({
|
|
419
|
-
...Attribute.WorkflowName(workflowName),
|
|
420
|
-
...Attribute.WorkflowRunId(workflowRunId),
|
|
421
|
-
...Attribute.StepId(stepId),
|
|
422
|
-
...Attribute.StepMaxRetries(stepFn.maxRetries ?? 3),
|
|
423
|
-
...Attribute.StepTracePropagated(!!traceContext),
|
|
424
|
-
});
|
|
425
|
-
let step = await world.steps.get(workflowRunId, stepId);
|
|
426
|
-
runtimeLogger.debug('Step execution details', {
|
|
427
|
-
stepName,
|
|
428
|
-
stepId: step.stepId,
|
|
429
|
-
status: step.status,
|
|
430
|
-
attempt: step.attempt,
|
|
431
|
-
});
|
|
432
|
-
span?.setAttributes({
|
|
433
|
-
...Attribute.StepStatus(step.status),
|
|
434
|
-
});
|
|
435
|
-
// Check if the step has a `retryAfter` timestamp that hasn't been reached yet
|
|
436
|
-
const now = Date.now();
|
|
437
|
-
if (step.retryAfter && step.retryAfter.getTime() > now) {
|
|
438
|
-
const timeoutSeconds = Math.ceil((step.retryAfter.getTime() - now) / 1000);
|
|
439
|
-
span?.setAttributes({
|
|
440
|
-
...Attribute.StepRetryTimeoutSeconds(timeoutSeconds),
|
|
441
|
-
});
|
|
442
|
-
runtimeLogger.debug('Step retryAfter timestamp not yet reached', {
|
|
443
|
-
stepName,
|
|
444
|
-
stepId: step.stepId,
|
|
445
|
-
retryAfter: step.retryAfter,
|
|
446
|
-
timeoutSeconds,
|
|
447
|
-
});
|
|
448
|
-
return { timeoutSeconds };
|
|
449
|
-
}
|
|
450
|
-
let result;
|
|
451
|
-
const attempt = step.attempt + 1;
|
|
452
|
-
try {
|
|
453
|
-
if (step.status !== 'pending') {
|
|
454
|
-
// We should only be running the step if it's pending
|
|
455
|
-
// (initial state, or state set on re-try), so the step has been
|
|
456
|
-
// invoked erroneously.
|
|
457
|
-
console.error(`[Workflows] "${workflowRunId}" - Step invoked erroneously, expected status "pending", got "${step.status}" instead, skipping execution`);
|
|
458
|
-
span?.setAttributes({
|
|
459
|
-
...Attribute.StepSkipped(true),
|
|
460
|
-
...Attribute.StepSkipReason(step.status),
|
|
461
|
-
});
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
await world.events.create(workflowRunId, {
|
|
465
|
-
eventType: 'step_started', // TODO: Replace with 'step_retrying'
|
|
466
|
-
correlationId: stepId,
|
|
467
|
-
});
|
|
468
|
-
step = await world.steps.update(workflowRunId, stepId, {
|
|
469
|
-
attempt,
|
|
470
|
-
status: 'running',
|
|
471
|
-
});
|
|
472
|
-
if (!step.startedAt) {
|
|
473
|
-
throw new WorkflowRuntimeError(`Step "${stepId}" has no "startedAt" timestamp`);
|
|
474
|
-
}
|
|
475
|
-
// Hydrate the step input arguments
|
|
476
|
-
const ops = [];
|
|
477
|
-
const args = hydrateStepArguments(step.input, ops);
|
|
478
|
-
span?.setAttributes({
|
|
479
|
-
...Attribute.StepArgumentsCount(args.length),
|
|
480
|
-
});
|
|
481
|
-
result = await contextStorage.run({
|
|
482
|
-
stepMetadata: {
|
|
483
|
-
stepId,
|
|
484
|
-
stepStartedAt: new Date(+step.startedAt),
|
|
485
|
-
attempt,
|
|
486
|
-
},
|
|
487
|
-
workflowMetadata: {
|
|
488
|
-
workflowRunId,
|
|
489
|
-
workflowStartedAt: new Date(+workflowStartedAt),
|
|
490
|
-
// TODO: there should be a getUrl method on the world interface itself. This
|
|
491
|
-
// solution only works for vercel + embedded worlds.
|
|
492
|
-
url: process.env.VERCEL_URL
|
|
493
|
-
? `https://${process.env.VERCEL_URL}`
|
|
494
|
-
: `http://localhost:${process.env.PORT || 3000}`,
|
|
495
|
-
},
|
|
496
|
-
}, () => stepFn(...args));
|
|
497
|
-
result = dehydrateStepReturnValue(result, ops);
|
|
498
|
-
waitUntil(Promise.all(ops));
|
|
499
|
-
// Update the event log with the step result
|
|
500
|
-
await world.events.create(workflowRunId, {
|
|
501
|
-
eventType: 'step_completed',
|
|
502
|
-
correlationId: stepId,
|
|
503
|
-
eventData: {
|
|
504
|
-
result: result,
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
await world.steps.update(workflowRunId, stepId, {
|
|
508
|
-
status: 'completed',
|
|
509
|
-
output: result,
|
|
510
|
-
});
|
|
511
|
-
span?.setAttributes({
|
|
512
|
-
...Attribute.StepStatus('completed'),
|
|
513
|
-
...Attribute.StepResultType(typeof result),
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
catch (err) {
|
|
517
|
-
span?.setAttributes({
|
|
518
|
-
...Attribute.StepErrorName(getErrorName(err)),
|
|
519
|
-
...Attribute.StepErrorMessage(String(err)),
|
|
520
|
-
});
|
|
521
|
-
if (WorkflowAPIError.is(err)) {
|
|
522
|
-
if (err.status === 410) {
|
|
523
|
-
// Workflow has already completed, so no-op
|
|
524
|
-
console.warn(`Workflow run "${workflowRunId}" has already completed, skipping step "${stepId}": ${err.message}`);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
if (FatalError.is(err)) {
|
|
529
|
-
const stackLines = getErrorStack(err).split('\n').slice(0, 4);
|
|
530
|
-
console.error(`[Workflows] "${workflowRunId}" - Encountered \`FatalError\` while executing step "${stepName}":\n > ${stackLines.join('\n > ')}\n\nBubbling up error to parent workflow`);
|
|
531
|
-
// Fatal error - store the error in the event log and re-invoke the workflow
|
|
532
|
-
await world.events.create(workflowRunId, {
|
|
533
|
-
eventType: 'step_failed',
|
|
534
|
-
correlationId: stepId,
|
|
535
|
-
eventData: {
|
|
536
|
-
error: String(err),
|
|
537
|
-
stack: err.stack,
|
|
538
|
-
fatal: true,
|
|
539
|
-
},
|
|
540
|
-
});
|
|
541
|
-
await world.steps.update(workflowRunId, stepId, {
|
|
542
|
-
status: 'failed',
|
|
543
|
-
error: String(err),
|
|
544
|
-
// TODO: include error codes when we define them
|
|
545
|
-
// TODO: serialize/include the error name and stack?
|
|
546
|
-
});
|
|
547
|
-
span?.setAttributes({
|
|
548
|
-
...Attribute.StepStatus('failed'),
|
|
549
|
-
...Attribute.StepFatalError(true),
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
const maxRetries = stepFn.maxRetries ?? 3;
|
|
554
|
-
span?.setAttributes({
|
|
555
|
-
...Attribute.StepAttempt(attempt),
|
|
556
|
-
...Attribute.StepMaxRetries(maxRetries),
|
|
557
|
-
});
|
|
558
|
-
if (attempt >= maxRetries) {
|
|
559
|
-
// Max retries reached
|
|
560
|
-
const stackLines = getErrorStack(err).split('\n').slice(0, 4);
|
|
561
|
-
console.error(`[Workflows] "${workflowRunId}" - Encountered \`Error\` while executing step "${stepName}" (attempt ${attempt}):\n > ${stackLines.join('\n > ')}\n\n Max retries reached\n Bubbling error to parent workflow`);
|
|
562
|
-
const errorMessage = `Step "${stepName}" failed after max retries: ${String(err)}`;
|
|
563
|
-
await world.events.create(workflowRunId, {
|
|
564
|
-
eventType: 'step_failed',
|
|
565
|
-
correlationId: stepId,
|
|
566
|
-
eventData: {
|
|
567
|
-
error: errorMessage,
|
|
568
|
-
stack: getErrorStack(err),
|
|
569
|
-
fatal: true,
|
|
570
|
-
},
|
|
571
|
-
});
|
|
572
|
-
await world.steps.update(workflowRunId, stepId, {
|
|
573
|
-
status: 'failed',
|
|
574
|
-
error: errorMessage,
|
|
575
|
-
});
|
|
576
|
-
span?.setAttributes({
|
|
577
|
-
...Attribute.StepStatus('failed'),
|
|
578
|
-
...Attribute.StepRetryExhausted(true),
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
// Not at max retries yet - log as a retryable error
|
|
583
|
-
if (RetryableError.is(err)) {
|
|
584
|
-
console.warn(`[Workflows] "${workflowRunId}" - Encountered \`RetryableError\` while executing step "${stepName}" (attempt ${attempt}):\n > ${String(err.message)}\n\n This step has failed but will be retried`);
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
const stackLines = getErrorStack(err).split('\n').slice(0, 4);
|
|
588
|
-
console.error(`[Workflows] "${workflowRunId}" - Encountered \`Error\` while executing step "${stepName}" (attempt ${attempt}):\n > ${stackLines.join('\n > ')}\n\n This step has failed but will be retried`);
|
|
589
|
-
}
|
|
590
|
-
await world.events.create(workflowRunId, {
|
|
591
|
-
eventType: 'step_failed',
|
|
592
|
-
correlationId: stepId,
|
|
593
|
-
eventData: {
|
|
594
|
-
error: String(err),
|
|
595
|
-
stack: getErrorStack(err),
|
|
596
|
-
},
|
|
597
|
-
});
|
|
598
|
-
await world.steps.update(workflowRunId, stepId, {
|
|
599
|
-
status: 'pending', // TODO: Should be "retrying" once we have that status
|
|
600
|
-
...(RetryableError.is(err) && {
|
|
601
|
-
retryAfter: err.retryAfter,
|
|
602
|
-
}),
|
|
603
|
-
});
|
|
604
|
-
const timeoutSeconds = Math.max(1, RetryableError.is(err)
|
|
605
|
-
? Math.ceil((+err.retryAfter.getTime() - Date.now()) / 1000)
|
|
606
|
-
: 1);
|
|
607
|
-
span?.setAttributes({
|
|
608
|
-
...Attribute.StepRetryTimeoutSeconds(timeoutSeconds),
|
|
609
|
-
...Attribute.StepRetryWillRetry(true),
|
|
610
|
-
});
|
|
611
|
-
// It's a retryable error - so have the queue keep the message visible
|
|
612
|
-
// so that it gets retried.
|
|
613
|
-
return { timeoutSeconds };
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
await world.queue(`__wkf_workflow_${workflowName}`, {
|
|
618
|
-
runId: workflowRunId,
|
|
619
|
-
traceCarrier: await serializeTraceCarrier(),
|
|
620
|
-
});
|
|
621
|
-
});
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
317
|
// this is a no-op placeholder as the client is
|
|
625
318
|
// expecting this to be present but we aren't actually using it
|
|
626
319
|
export function runStep() { }
|
|
627
|
-
//# sourceMappingURL=runtime.js.map
|
|
320
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9ydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCx5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3RCLDRCQUE0QixFQUM1QixvQkFBb0IsR0FDckIsTUFBTSxrQkFBa0IsQ0FBQztBQUMxQixPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMvRCxPQUFPLEVBRUwsb0JBQW9CLEVBQ3BCLDJCQUEyQixHQUk1QixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNqRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQzVDLE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIsZ0JBQWdCLEVBQ2hCLHdCQUF3QixFQUN4Qix1QkFBdUIsRUFDdkIsZUFBZSxHQUNoQixNQUFNLHNCQUFzQixDQUFDO0FBQzlCLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxRQUFRLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLDBCQUEwQixHQUMzQixNQUFNLG9CQUFvQixDQUFDO0FBQzVCLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNsRCxPQUFPLEtBQUssU0FBUyxNQUFNLHFDQUFxQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMvRSxPQUFPLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN6RCxPQUFPLEVBQ0wsOEJBQThCLEVBQzlCLHNCQUFzQixHQUN2QixNQUFNLFdBQVcsQ0FBQztBQUNuQixPQUFPLEVB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
|