@lunora/workflow 1.0.0-alpha.8 → 1.0.0-alpha.9
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/do/index.mjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/packem_shared/{MAX_BRANCHES-D6cQabJ1.mjs → MAX_BRANCHES-EO5xYS_3.mjs} +55 -17
- package/dist/packem_shared/{createWorkflowContext-DfSgBq5I.mjs → createWorkflowContext-oxZ5zj3x.mjs} +1 -1
- package/dist/packem_shared/{createWorkflowRunContext-C09mSbgk.mjs → createWorkflowRunContext-BDSHE8iO.mjs} +18 -3
- package/dist/packem_shared/{createWorkflows-BC1Mwjwe.mjs → createWorkflows-wq4fs72q.mjs} +17 -2
- package/package.json +3 -3
package/dist/do/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
2
2
|
import { NonRetryableError } from 'cloudflare:workflows';
|
|
3
3
|
import { convertNonRetryableError } from '../packem_shared/NonRetryableError-Dn2dTyBS.mjs';
|
|
4
|
-
import {
|
|
5
|
-
import { createWorkflowRunContext } from '../packem_shared/createWorkflowRunContext-
|
|
4
|
+
import { extractBranchMarker, stripBranchMarker, signalBranchParentSafe, errorOutcome, okOutcome } from '../packem_shared/MAX_BRANCHES-EO5xYS_3.mjs';
|
|
5
|
+
import { createWorkflowRunContext } from '../packem_shared/createWorkflowRunContext-BDSHE8iO.mjs';
|
|
6
6
|
|
|
7
7
|
class LunoraWorkflow extends WorkflowEntrypoint {
|
|
8
8
|
/** The `lunora/workflows.ts` export name, for log correlation. */
|
|
@@ -30,12 +30,12 @@ class LunoraWorkflow extends WorkflowEntrypoint {
|
|
|
30
30
|
output = await this.#definition.handler(context);
|
|
31
31
|
} catch (error) {
|
|
32
32
|
if (marker) {
|
|
33
|
-
await
|
|
33
|
+
await signalBranchParentSafe({ env: this.env, log: context.log, step: nativeStep }, marker, errorOutcome(error));
|
|
34
34
|
}
|
|
35
35
|
return convertNonRetryableError(error, NonRetryableError);
|
|
36
36
|
}
|
|
37
37
|
if (marker) {
|
|
38
|
-
await
|
|
38
|
+
await signalBranchParentSafe({ env: this.env, log: context.log, step: nativeStep }, marker, okOutcome(output));
|
|
39
39
|
}
|
|
40
40
|
return output;
|
|
41
41
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { createWorkflowContext } from './packem_shared/createWorkflowContext-
|
|
2
|
-
export { default as createWorkflows } from './packem_shared/createWorkflows-
|
|
1
|
+
export { createWorkflowContext } from './packem_shared/createWorkflowContext-oxZ5zj3x.mjs';
|
|
2
|
+
export { default as createWorkflows } from './packem_shared/createWorkflows-wq4fs72q.mjs';
|
|
3
3
|
export { defineStep, isStepDefinition } from './packem_shared/defineStep-DJQtLw7g.mjs';
|
|
4
4
|
export { defineWorkflow, isWorkflowDefinition, workflowBindingName, workflowClassName, workflowDefaultName } from './packem_shared/defineWorkflow-DbUC-oCN.mjs';
|
|
5
5
|
export { NonRetryableError, convertNonRetryableError, isNonRetryableError, toNativeNonRetryableError } from './packem_shared/NonRetryableError-Dn2dTyBS.mjs';
|
|
6
|
-
export { MAX_BRANCHES, branch } from './packem_shared/MAX_BRANCHES-
|
|
6
|
+
export { MAX_BRANCHES, branch } from './packem_shared/MAX_BRANCHES-EO5xYS_3.mjs';
|
|
7
7
|
export { WorkflowsRestError, createWorkflowsRestClient } from './packem_shared/WorkflowsRestError-zmjOxTR1.mjs';
|
|
8
|
-
export { createWorkflowRunContext } from './packem_shared/createWorkflowRunContext-
|
|
8
|
+
export { createWorkflowRunContext } from './packem_shared/createWorkflowRunContext-BDSHE8iO.mjs';
|
|
9
9
|
export { createRunStep, validateStepArgs } from './packem_shared/createRunStep-8jOXxP2o.mjs';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { NonRetryableError } from './NonRetryableError-Dn2dTyBS.mjs';
|
|
2
3
|
|
|
3
4
|
const MAX_BRANCHES = 100;
|
|
@@ -29,17 +30,25 @@ const compensateCompleted = async (deps, completed, error) => {
|
|
|
29
30
|
if (done === void 0 || compensateWith === void 0) {
|
|
30
31
|
continue;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
try {
|
|
34
|
+
const compensation = deps.resolveBinding(compensateWith);
|
|
35
|
+
await deps.step.do(`${COMPENSATE_STEP_PREFIX}${done.plan.childId}`, async () => {
|
|
36
|
+
const compensateId = `${done.plan.childId}:compensate`;
|
|
37
|
+
const compensationParams = {
|
|
38
|
+
branch: done.plan.item.workflow,
|
|
39
|
+
error,
|
|
40
|
+
index: done.plan.index,
|
|
41
|
+
output: done.output
|
|
42
|
+
};
|
|
43
|
+
await compensation.create({ id: compensateId, params: compensationParams });
|
|
44
|
+
return compensateId;
|
|
45
|
+
});
|
|
46
|
+
} catch (compensationError) {
|
|
47
|
+
deps.log?.error(
|
|
48
|
+
`ctx.parallel: group-saga compensation "${compensateWith}" for branch "${done.plan.item.workflow}" (#${String(done.plan.index)}) failed`,
|
|
49
|
+
compensationError
|
|
50
|
+
);
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
53
|
};
|
|
45
54
|
const createParallel = (deps) => {
|
|
@@ -56,6 +65,15 @@ const createParallel = (deps) => {
|
|
|
56
65
|
const childId = deps.nextChildId(item.id);
|
|
57
66
|
return { childId, eventType: `${BRANCH_EVENT_PREFIX}${childId}`, index, item };
|
|
58
67
|
});
|
|
68
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
69
|
+
for (const plan of planned) {
|
|
70
|
+
if (seenIds.has(plan.childId)) {
|
|
71
|
+
throw new NonRetryableError(
|
|
72
|
+
`ctx.parallel: duplicate branch id "${plan.childId}" — each branch in a group must resolve to a unique child instance id (check explicit \`id\` options)`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
seenIds.add(plan.childId);
|
|
76
|
+
}
|
|
59
77
|
await Promise.all(
|
|
60
78
|
planned.map(
|
|
61
79
|
(plan) => deps.step.do(`${SPAWN_STEP_PREFIX}${plan.childId}`, async () => {
|
|
@@ -69,11 +87,18 @@ const createParallel = (deps) => {
|
|
|
69
87
|
const results = [];
|
|
70
88
|
const completed = [];
|
|
71
89
|
for (const plan of planned) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
let outcome;
|
|
91
|
+
try {
|
|
92
|
+
const event = await deps.step.waitForEvent(`${AWAIT_STEP_PREFIX}${plan.childId}`, {
|
|
93
|
+
timeout: plan.item.timeout,
|
|
94
|
+
type: plan.eventType
|
|
95
|
+
});
|
|
96
|
+
outcome = event.payload;
|
|
97
|
+
} catch (joinError) {
|
|
98
|
+
const joinFailure = serializeError(joinError);
|
|
99
|
+
await compensateCompleted(deps, completed, joinFailure);
|
|
100
|
+
throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) join failed: ${joinFailure.message}`);
|
|
101
|
+
}
|
|
77
102
|
if (outcome.status === "error") {
|
|
78
103
|
await compensateCompleted(deps, completed, outcome.error);
|
|
79
104
|
throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) failed: ${outcome.error.message}`);
|
|
@@ -86,6 +111,9 @@ const createParallel = (deps) => {
|
|
|
86
111
|
return run;
|
|
87
112
|
};
|
|
88
113
|
const createSpawn = (deps) => async (workflow, params, options) => {
|
|
114
|
+
if (params !== void 0 && Object.hasOwn(params, BRANCH_MARKER_KEY)) {
|
|
115
|
+
throw new LunoraError("BAD_REQUEST", `@lunora/workflow: params may not contain the reserved key "${BRANCH_MARKER_KEY}"`);
|
|
116
|
+
}
|
|
89
117
|
const childId = deps.nextChildId(options?.id);
|
|
90
118
|
await deps.step.do(`${SPAWN_STEP_PREFIX}${childId}`, async () => {
|
|
91
119
|
const binding = deps.resolveBinding(workflow);
|
|
@@ -106,6 +134,9 @@ const extractBranchMarker = (payload) => {
|
|
|
106
134
|
if (typeof candidate.eventType !== "string" || typeof candidate.parentBinding !== "string" || typeof candidate.parentId !== "string" || typeof candidate.index !== "number") {
|
|
107
135
|
return void 0;
|
|
108
136
|
}
|
|
137
|
+
if (!candidate.parentBinding.startsWith("WORKFLOW_") || !candidate.eventType.startsWith(BRANCH_EVENT_PREFIX)) {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
109
140
|
return { eventType: candidate.eventType, index: candidate.index, parentBinding: candidate.parentBinding, parentId: candidate.parentId };
|
|
110
141
|
};
|
|
111
142
|
const stripBranchMarker = (payload) => {
|
|
@@ -128,5 +159,12 @@ const signalBranchParent = async (deps, marker, outcome) => {
|
|
|
128
159
|
return marker.eventType;
|
|
129
160
|
});
|
|
130
161
|
};
|
|
162
|
+
const signalBranchParentSafe = async (deps, marker, outcome) => {
|
|
163
|
+
try {
|
|
164
|
+
await signalBranchParent(deps, marker, outcome);
|
|
165
|
+
} catch (signalError) {
|
|
166
|
+
deps.log?.error(`@lunora/workflow: failed to signal branch parent "${marker.parentId}" (event "${marker.eventType}")`, signalError);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
131
169
|
|
|
132
|
-
export { BRANCH_MARKER_KEY, MAX_BRANCHES, branch, createParallel, createSpawn, errorOutcome, extractBranchMarker, okOutcome, signalBranchParent, stripBranchMarker };
|
|
170
|
+
export { BRANCH_MARKER_KEY, MAX_BRANCHES, branch, createParallel, createSpawn, errorOutcome, extractBranchMarker, okOutcome, signalBranchParent, signalBranchParentSafe, stripBranchMarker };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
import { workflowBindingName } from './defineWorkflow-DbUC-oCN.mjs';
|
|
3
|
-
import { createSpawn, createParallel } from './MAX_BRANCHES-
|
|
3
|
+
import { createSpawn, createParallel } from './MAX_BRANCHES-EO5xYS_3.mjs';
|
|
4
4
|
import { createRunStep } from './createRunStep-8jOXxP2o.mjs';
|
|
5
5
|
|
|
6
6
|
const createDispatchLogger = (prefix) => {
|
|
@@ -28,6 +28,18 @@ const trimTrailingSlashes = (value) => {
|
|
|
28
28
|
}
|
|
29
29
|
return value.slice(0, end);
|
|
30
30
|
};
|
|
31
|
+
const toDispatchError = (label, status, rawBody) => {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(rawBody);
|
|
34
|
+
const errorBody = parsed?.error;
|
|
35
|
+
if (typeof errorBody === "object" && errorBody !== null && typeof errorBody.code === "string") {
|
|
36
|
+
const { code, data, message } = errorBody;
|
|
37
|
+
return new LunoraError(code, typeof message === "string" ? message : void 0, { data, status });
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
return new LunoraError("INTERNAL", `${label}: function dispatch failed (${String(status)}): ${rawBody}`, { status });
|
|
42
|
+
};
|
|
31
43
|
const createDispatchRunner = (options) => {
|
|
32
44
|
const { label } = options;
|
|
33
45
|
const globalFetch = globalThis.fetch;
|
|
@@ -51,7 +63,7 @@ const createDispatchRunner = (options) => {
|
|
|
51
63
|
method: "POST"
|
|
52
64
|
});
|
|
53
65
|
if (!response.ok) {
|
|
54
|
-
throw
|
|
66
|
+
throw toDispatchError(label, response.status, await response.text());
|
|
55
67
|
}
|
|
56
68
|
const text = await response.text();
|
|
57
69
|
if (text.length === 0) {
|
|
@@ -60,7 +72,9 @@ const createDispatchRunner = (options) => {
|
|
|
60
72
|
try {
|
|
61
73
|
return JSON.parse(text);
|
|
62
74
|
} catch {
|
|
63
|
-
|
|
75
|
+
throw new LunoraError("INTERNAL", `${label}: function dispatch returned a non-JSON body (${String(response.status)}): ${text}`, {
|
|
76
|
+
status: response.status
|
|
77
|
+
});
|
|
64
78
|
}
|
|
65
79
|
};
|
|
66
80
|
};
|
|
@@ -91,6 +105,7 @@ const createWorkflowRunContext = (options) => {
|
|
|
91
105
|
const fanOutDeps = {
|
|
92
106
|
env: options.env,
|
|
93
107
|
instanceId: options.event.instanceId,
|
|
108
|
+
log,
|
|
94
109
|
nextChildId,
|
|
95
110
|
parentBinding: workflowBindingName(options.exportName),
|
|
96
111
|
resolveBinding,
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { BRANCH_MARKER_KEY } from './MAX_BRANCHES-EO5xYS_3.mjs';
|
|
2
3
|
|
|
4
|
+
const rejectReservedParams = (options) => {
|
|
5
|
+
const { params } = options ?? {};
|
|
6
|
+
if (params !== void 0 && Object.hasOwn(params, BRANCH_MARKER_KEY)) {
|
|
7
|
+
throw new LunoraError("BAD_REQUEST", `@lunora/workflow: params may not contain the reserved key "${BRANCH_MARKER_KEY}"`);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
3
10
|
const handleFor = (binding) => {
|
|
4
11
|
return {
|
|
5
|
-
create: async (options) =>
|
|
6
|
-
|
|
12
|
+
create: async (options) => {
|
|
13
|
+
rejectReservedParams(options);
|
|
14
|
+
return binding.create(options);
|
|
15
|
+
},
|
|
16
|
+
createBatch: async (batch) => {
|
|
17
|
+
for (const options of batch) {
|
|
18
|
+
rejectReservedParams(options);
|
|
19
|
+
}
|
|
20
|
+
return binding.createBatch(batch);
|
|
21
|
+
},
|
|
7
22
|
get: async (id) => binding.get(id)
|
|
8
23
|
};
|
|
9
24
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/workflow",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.9",
|
|
4
4
|
"description": "Durable workflows for Lunora: defineWorkflow over Cloudflare Workflows, generated WorkflowEntrypoint classes, and the ctx.workflows surface",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
52
|
-
"@lunora/values": "1.0.0-alpha.
|
|
51
|
+
"@lunora/errors": "1.0.0-alpha.4",
|
|
52
|
+
"@lunora/values": "1.0.0-alpha.7"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": "^22.15.0 || >=24.11.0"
|