yieldstar 0.4.6 → 0.6.0
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 +57 -64
- package/dist/exports/errors.d.ts.map +1 -1
- package/dist/exports/sdk-http.d.ts +8 -0
- package/dist/exports/sdk-http.d.ts.map +1 -1
- package/dist/exports/sdk-local.d.ts +19 -2
- package/dist/exports/sdk-local.d.ts.map +1 -1
- package/dist/exports/workflow.d.ts +14 -2
- package/dist/exports/workflow.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +791 -137
- package/dist/internal/effect-call.d.ts +60 -0
- package/dist/internal/effect-call.d.ts.map +1 -0
- package/dist/internal/runtime-call.d.ts +39 -0
- package/dist/internal/runtime-call.d.ts.map +1 -0
- package/dist/internal/serialise.d.ts.map +1 -1
- package/dist/internal/step-record.d.ts +13 -0
- package/dist/internal/step-record.d.ts.map +1 -0
- package/dist/internal/step-runner.d.ts +93 -9
- package/dist/internal/step-runner.d.ts.map +1 -1
- package/dist/internal/utils.d.ts +11 -0
- package/dist/internal/utils.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/exports/errors.ts +4 -0
- package/src/exports/sdk-http.ts +60 -27
- package/src/exports/sdk-local.ts +41 -15
- package/src/exports/workflow.ts +159 -99
- package/src/index.ts +51 -1
- package/src/internal/effect-call.ts +217 -0
- package/src/internal/runtime-call.ts +95 -0
- package/src/internal/step-record.ts +110 -0
- package/src/internal/step-runner.ts +748 -53
- package/src/internal/utils.ts +24 -10
- package/dist/exports/scheduler.d.ts +0 -49
- package/dist/exports/scheduler.d.ts.map +0 -1
- package/dist/exports/simplified-types.d.ts +0 -31
- package/dist/exports/simplified-types.d.ts.map +0 -1
- package/dist/exports/types.d.ts +0 -6
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/internal/types.d.ts +0 -9
- package/dist/internal/types.d.ts.map +0 -1
- package/src/internal/serialise.ts +0 -44
- package/tsconfig.tsbuildinfo +0 -1
package/dist/index.js
CHANGED
|
@@ -1,57 +1,83 @@
|
|
|
1
|
-
// @bun
|
|
2
1
|
// src/exports/sdk-local.ts
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
ExecutionAdmissionError,
|
|
4
|
+
waitForExecutionOutcome
|
|
5
|
+
} from "@yieldstar/core";
|
|
6
|
+
function createLocalSdk(runtime, options) {
|
|
7
|
+
const { executions } = runtime;
|
|
5
8
|
return {
|
|
6
9
|
async trigger(event) {
|
|
7
|
-
const
|
|
8
|
-
executionId: event.executionId
|
|
10
|
+
const admission = await executions.admit({
|
|
11
|
+
executionId: event.executionId,
|
|
9
12
|
workflowId: event.workflowId,
|
|
10
|
-
params: event.params
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
params: event.params,
|
|
14
|
+
context: new Map(Object.entries(event.context ?? {}))
|
|
15
|
+
});
|
|
16
|
+
if (admission.status === "rejected") {
|
|
17
|
+
throw new ExecutionAdmissionError(admission.code, admission.message);
|
|
18
|
+
}
|
|
19
|
+
return { executionId: admission.executionId };
|
|
14
20
|
},
|
|
15
21
|
async triggerAndWait(event) {
|
|
16
|
-
const executionId =
|
|
17
|
-
const
|
|
18
|
-
invoker
|
|
22
|
+
const { executionId } = await this.trigger(event);
|
|
23
|
+
const outcome = await waitForExecutionOutcome(executions, executionId, {
|
|
24
|
+
emitter: options?.invoker?.workflowEndEmitter,
|
|
25
|
+
pollInterval: options?.pollInterval
|
|
19
26
|
});
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
if (!outcome) {
|
|
28
|
+
throw new Error(`Execution "${executionId}" has no record`);
|
|
29
|
+
}
|
|
30
|
+
if (outcome.status === "failed")
|
|
31
|
+
throw outcome.error;
|
|
32
|
+
return outcome.result;
|
|
22
33
|
}
|
|
23
34
|
};
|
|
24
35
|
}
|
|
25
36
|
// src/exports/sdk-http.ts
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
import {
|
|
38
|
+
ExecutionAdmissionError as ExecutionAdmissionError2,
|
|
39
|
+
durableValueCodec
|
|
40
|
+
} from "@yieldstar/core";
|
|
41
|
+
var admissionErrorCodes = [
|
|
42
|
+
"EXECUTION_ID_CONFLICT",
|
|
43
|
+
"INVALID_EXECUTION_EVENT"
|
|
44
|
+
];
|
|
29
45
|
function createHttpSdkFactory() {
|
|
30
46
|
return (params) => {
|
|
31
47
|
return {
|
|
32
48
|
async trigger(event) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
const request = { workflowId: event?.workflowId };
|
|
50
|
+
if (event?.executionId !== undefined)
|
|
51
|
+
request.executionId = event.executionId;
|
|
52
|
+
if (event && "params" in event)
|
|
53
|
+
request.params = event.params;
|
|
54
|
+
if (event?.context !== undefined)
|
|
55
|
+
request.context = event.context;
|
|
56
|
+
const encoding = durableValueCodec.encode(request);
|
|
57
|
+
if (encoding.status === "invalid") {
|
|
58
|
+
throw new ExecutionAdmissionError2("INVALID_EXECUTION_EVENT", `Event cannot be stored without loss: ${encoding.message}`);
|
|
59
|
+
}
|
|
39
60
|
const res = await fetch(`${params.url}/trigger`, {
|
|
40
61
|
...params.fetchOptions,
|
|
41
62
|
method: "POST",
|
|
42
|
-
|
|
63
|
+
headers: { "content-type": "application/json" },
|
|
64
|
+
body: encoding.canonical
|
|
43
65
|
});
|
|
44
66
|
if (res.ok) {
|
|
67
|
+
const ack = await res.json();
|
|
45
68
|
return {
|
|
46
|
-
executionId:
|
|
47
|
-
ack: () =>
|
|
48
|
-
waitForResult: () => this.waitForResult(
|
|
49
|
-
executionId: executionEvent.executionId
|
|
50
|
-
})
|
|
69
|
+
executionId: ack.executionId,
|
|
70
|
+
ack: async () => ack,
|
|
71
|
+
waitForResult: () => this.waitForResult(ack)
|
|
51
72
|
};
|
|
52
|
-
} else {
|
|
53
|
-
throw res.statusText;
|
|
54
73
|
}
|
|
74
|
+
const body = await res.json().catch(() => {
|
|
75
|
+
return;
|
|
76
|
+
});
|
|
77
|
+
if (body?.code && admissionErrorCodes.includes(body.code)) {
|
|
78
|
+
throw new ExecutionAdmissionError2(body.code, body.message ?? res.statusText);
|
|
79
|
+
}
|
|
80
|
+
throw res.statusText;
|
|
55
81
|
},
|
|
56
82
|
async waitForResult(ack) {
|
|
57
83
|
const res = await fetch(`${params.url}/events`, {
|
|
@@ -59,11 +85,18 @@ function createHttpSdkFactory() {
|
|
|
59
85
|
body: JSON.stringify({ executionId: ack.executionId }),
|
|
60
86
|
...params.fetchOptions
|
|
61
87
|
});
|
|
88
|
+
if (res.status !== 500 && !res.ok) {
|
|
89
|
+
throw res.statusText;
|
|
90
|
+
}
|
|
91
|
+
const decoding = durableValueCodec.decode(await res.text());
|
|
92
|
+
if (decoding.status === "invalid") {
|
|
93
|
+
throw new Error(`Invalid workflow outcome: ${decoding.message}`);
|
|
94
|
+
}
|
|
62
95
|
if (res.status === 500) {
|
|
63
|
-
const error =
|
|
64
|
-
throw
|
|
96
|
+
const error = decoding.value instanceof Error ? decoding.value : new Error(String(decoding.value));
|
|
97
|
+
throw error;
|
|
65
98
|
}
|
|
66
|
-
return
|
|
99
|
+
return decoding.value;
|
|
67
100
|
}
|
|
68
101
|
};
|
|
69
102
|
};
|
|
@@ -73,6 +106,8 @@ function createWorkflowRouter(workflows) {
|
|
|
73
106
|
return workflows;
|
|
74
107
|
}
|
|
75
108
|
// src/exports/errors.ts
|
|
109
|
+
import { registerErrorClass } from "@yieldstar/core";
|
|
110
|
+
|
|
76
111
|
class RetryableError extends Error {
|
|
77
112
|
maxAttempts;
|
|
78
113
|
retryInterval;
|
|
@@ -83,129 +118,537 @@ class RetryableError extends Error {
|
|
|
83
118
|
this.retryInterval = opts.retryInterval;
|
|
84
119
|
}
|
|
85
120
|
}
|
|
121
|
+
registerErrorClass("RetryableError", RetryableError);
|
|
122
|
+
|
|
123
|
+
// src/index.ts
|
|
124
|
+
import {
|
|
125
|
+
EffectClientInvariantError as EffectClientInvariantError2,
|
|
126
|
+
EffectDeliveryError as EffectDeliveryError2,
|
|
127
|
+
ExecutionAdmissionError as ExecutionAdmissionError3,
|
|
128
|
+
WORKFLOW_RESULT_STEP_KEY as WORKFLOW_RESULT_STEP_KEY2,
|
|
129
|
+
WorkflowEffectError as WorkflowEffectError2,
|
|
130
|
+
WorkflowStartError as WorkflowStartError2,
|
|
131
|
+
WorkflowValueError as WorkflowValueError4,
|
|
132
|
+
registerErrorClass as registerErrorClass2
|
|
133
|
+
} from "@yieldstar/core";
|
|
134
|
+
|
|
86
135
|
// src/internal/utils.ts
|
|
87
136
|
import { createHash } from "crypto";
|
|
88
137
|
var isIterable = (i) => {
|
|
89
138
|
return typeof i === "object" && i != null && Symbol.iterator in i;
|
|
90
139
|
};
|
|
91
140
|
function getCallSiteHash(stepFn) {
|
|
92
|
-
const
|
|
93
|
-
Error.captureStackTrace(
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
stepFn.stack = originalStack;
|
|
141
|
+
const holder = {};
|
|
142
|
+
Error.captureStackTrace(holder, stepFn);
|
|
143
|
+
const frames = (holder.stack ?? "").split(`
|
|
144
|
+
`).filter((line) => /^\s*at\s/.test(line));
|
|
145
|
+
const callSite = frames[0]?.trim();
|
|
146
|
+
if (!callSite) {
|
|
147
|
+
throw new Error("Could not derive a cache key from the call site. Pass an explicit key to this step.");
|
|
100
148
|
}
|
|
101
|
-
return createHash("sha1").update(
|
|
149
|
+
return createHash("sha1").update(callSite).digest("hex");
|
|
102
150
|
}
|
|
103
151
|
|
|
104
|
-
// src/internal/
|
|
152
|
+
// src/internal/step-record.ts
|
|
105
153
|
import {
|
|
154
|
+
DurableValueError,
|
|
106
155
|
StepDelay,
|
|
156
|
+
StepEffectWait,
|
|
107
157
|
StepError,
|
|
158
|
+
StepInvalid,
|
|
108
159
|
StepResult,
|
|
109
|
-
|
|
160
|
+
StepStoreWait,
|
|
161
|
+
WorkflowResult,
|
|
162
|
+
WorkflowValueError,
|
|
163
|
+
decodeParsedValue,
|
|
164
|
+
durableValueCodec as durableValueCodec2,
|
|
165
|
+
encodeThrownValue
|
|
110
166
|
} from "@yieldstar/core";
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
167
|
+
function encodeStepRecord(response, stepKey) {
|
|
168
|
+
const json = encodeResponse(response, stepKey);
|
|
169
|
+
return { json, response: decodeStepRecord(json) };
|
|
170
|
+
}
|
|
171
|
+
function encodeResponse(response, stepKey) {
|
|
172
|
+
if (response instanceof StepResult || response instanceof WorkflowResult) {
|
|
173
|
+
const result = durableValueCodec2.encode(response.result);
|
|
174
|
+
if (result.status === "invalid") {
|
|
175
|
+
const label = response instanceof WorkflowResult ? "Workflow result" : `Step "${stepKey}" result`;
|
|
176
|
+
return encodeResponse(new StepError(new WorkflowValueError("INVALID_STEP_VALUE", stepKey, `${label} is not a durable value: ${result.message}`)), stepKey);
|
|
116
177
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
178
|
+
const schema = response instanceof StepResult && response.schema !== undefined ? `,"schema":${JSON.stringify(response.schema)}` : "";
|
|
179
|
+
return `{"result":${result.canonical}${schema},"type":${JSON.stringify(response.type)}}`;
|
|
180
|
+
}
|
|
181
|
+
if (response instanceof StepError) {
|
|
182
|
+
return `{"backOffStrategy":${JSON.stringify(response.backOffStrategy)}` + `,"err":${encodeThrownValue(response.err)}` + `,"maxAttempts":${JSON.stringify(response.maxAttempts)}` + `,"retryInterval":${JSON.stringify(response.retryInterval)}` + `,"type":"step-error"}`;
|
|
183
|
+
}
|
|
184
|
+
if (response instanceof StepDelay) {
|
|
185
|
+
return `{"type":"step-delay","wakeAt":${JSON.stringify(response.wakeAt)}}`;
|
|
186
|
+
}
|
|
187
|
+
if (response instanceof StepEffectWait) {
|
|
188
|
+
return `{"type":"effect-wait","wakeAt":${JSON.stringify(response.wakeAt)}}`;
|
|
189
|
+
}
|
|
190
|
+
if (response instanceof StepStoreWait || response instanceof StepInvalid) {
|
|
191
|
+
return `{"type":${JSON.stringify(response.type)}}`;
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`Cannot record a ${response.type} step response`);
|
|
120
194
|
}
|
|
121
|
-
function
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
195
|
+
function decodeStepRecord(json) {
|
|
196
|
+
const record = JSON.parse(json);
|
|
197
|
+
try {
|
|
198
|
+
switch (record.type) {
|
|
199
|
+
case "step-result":
|
|
200
|
+
return new StepResult(decodeParsedValue(record.result, "result"), record.schema === "accepted" || record.schema === "rejected" ? record.schema : undefined);
|
|
201
|
+
case "workflow-result":
|
|
202
|
+
return new WorkflowResult(decodeParsedValue(record.result, "result"));
|
|
203
|
+
case "step-error":
|
|
204
|
+
return new StepError(decodeParsedValue(record.err, "err"), {
|
|
205
|
+
maxAttempts: record.maxAttempts,
|
|
206
|
+
retryInterval: record.retryInterval,
|
|
207
|
+
backoffStrategy: record.backOffStrategy
|
|
208
|
+
});
|
|
209
|
+
case "step-delay":
|
|
210
|
+
return new StepDelay(record.wakeAt);
|
|
211
|
+
case "effect-wait":
|
|
212
|
+
return new StepEffectWait(record.wakeAt);
|
|
213
|
+
case "store-wait":
|
|
214
|
+
return new StepStoreWait;
|
|
215
|
+
case "step-invalid":
|
|
216
|
+
return new StepInvalid;
|
|
125
217
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const { err, ...params } = stepResponse;
|
|
132
|
-
return new StepError(stepResponse.err, params);
|
|
133
|
-
case "step-delay":
|
|
134
|
-
return new StepDelay(stepResponse.resumeIn);
|
|
135
|
-
case "step-result":
|
|
136
|
-
return new StepResult(stepResponse.result);
|
|
137
|
-
case "workflow-result":
|
|
138
|
-
return new WorkflowResult(stepResponse.result);
|
|
139
|
-
default:
|
|
140
|
-
throw new Error("Unknown step result type: " + stepResponse.type);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof DurableValueError) {
|
|
220
|
+
throw new Error(`Invalid step record: ${err.message}`);
|
|
221
|
+
}
|
|
222
|
+
throw err;
|
|
141
223
|
}
|
|
224
|
+
throw new Error(`Unknown step record type: ${record.type}`);
|
|
142
225
|
}
|
|
143
226
|
|
|
144
227
|
// src/internal/step-runner.ts
|
|
145
228
|
import {
|
|
229
|
+
assertSynchronousClaim,
|
|
230
|
+
assertSynchronousUpdater,
|
|
231
|
+
cloneStoreState,
|
|
232
|
+
defineStore,
|
|
233
|
+
InfrastructureError as InfrastructureError3,
|
|
146
234
|
StepKey,
|
|
147
235
|
StepError as StepError2,
|
|
148
236
|
StepResult as StepResult2,
|
|
149
237
|
StepDelay as StepDelay2,
|
|
150
|
-
StepCacheCheck
|
|
238
|
+
StepCacheCheck,
|
|
239
|
+
StepStoreWait as StepStoreWait2,
|
|
240
|
+
trackStoreSelector,
|
|
241
|
+
durableValueCodec as durableValueCodec4,
|
|
242
|
+
StepEffectWait as StepEffectWait2,
|
|
243
|
+
validateEffectRequest,
|
|
244
|
+
WorkflowEffectError,
|
|
245
|
+
WorkflowStartError,
|
|
246
|
+
WorkflowValueError as WorkflowValueError3
|
|
151
247
|
} from "@yieldstar/core";
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
248
|
+
|
|
249
|
+
// src/internal/runtime-call.ts
|
|
250
|
+
import {
|
|
251
|
+
InfrastructureError,
|
|
252
|
+
StepResponse as StepResponse2,
|
|
253
|
+
StoreValidationError,
|
|
254
|
+
WorkflowValueError as WorkflowValueError2
|
|
255
|
+
} from "@yieldstar/core";
|
|
256
|
+
|
|
257
|
+
class StepRuntimeCall extends StepResponse2 {
|
|
258
|
+
static type = "runtime-call";
|
|
259
|
+
type = "runtime-call";
|
|
260
|
+
call;
|
|
261
|
+
constructor(call) {
|
|
262
|
+
super();
|
|
263
|
+
this.call = call;
|
|
161
264
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
class StepCallbackError extends Error {
|
|
268
|
+
constructor(cause) {
|
|
269
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
270
|
+
this.name = "StepCallbackError";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function workflowCallback(fn, assertResult) {
|
|
274
|
+
return (...args) => {
|
|
275
|
+
try {
|
|
276
|
+
const result = fn(...args);
|
|
277
|
+
assertResult?.(result);
|
|
278
|
+
return result;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
throw new StepCallbackError(err);
|
|
170
281
|
}
|
|
171
|
-
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function executeRuntimeCall(request, clients) {
|
|
285
|
+
try {
|
|
286
|
+
return { ok: true, value: await request.call(clients) };
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (InfrastructureError.is(err))
|
|
289
|
+
throw err;
|
|
290
|
+
if (err instanceof StepCallbackError)
|
|
291
|
+
return { ok: false, error: err.cause };
|
|
292
|
+
if (isWorkflowFacingError(err))
|
|
293
|
+
return { ok: false, error: err };
|
|
294
|
+
throw new InfrastructureError(err);
|
|
172
295
|
}
|
|
296
|
+
}
|
|
297
|
+
function isWorkflowFacingError(err) {
|
|
298
|
+
return err instanceof StoreValidationError || err instanceof WorkflowValueError2 || err instanceof Error && err.name === "StoreValidationError";
|
|
299
|
+
}
|
|
300
|
+
async function infrastructure(fn) {
|
|
173
301
|
try {
|
|
174
|
-
|
|
175
|
-
yield new StepResult2(result);
|
|
176
|
-
return result;
|
|
302
|
+
return await fn();
|
|
177
303
|
} catch (err) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
304
|
+
throw InfrastructureError.is(err) ? err : new InfrastructureError(err);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/internal/effect-call.ts
|
|
309
|
+
import {
|
|
310
|
+
EffectClientInvariantError,
|
|
311
|
+
EffectDeliveryError,
|
|
312
|
+
InfrastructureError as InfrastructureError2,
|
|
313
|
+
StepResponse as StepResponse3,
|
|
314
|
+
durableValueCodec as durableValueCodec3
|
|
315
|
+
} from "@yieldstar/core";
|
|
316
|
+
|
|
317
|
+
class StepEffectCall extends StepResponse3 {
|
|
318
|
+
static type = "effect-call";
|
|
319
|
+
type = "effect-call";
|
|
320
|
+
request;
|
|
321
|
+
constructor(request) {
|
|
322
|
+
super();
|
|
323
|
+
this.request = request;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function executeEffectCall(request, context) {
|
|
327
|
+
const { effects, executionId, clock, assertLease, logger } = context;
|
|
328
|
+
const { stepKey, effectId, operation, input, retryIn } = request;
|
|
329
|
+
const fail = (code, message, cause) => new InfrastructureError2(new EffectDeliveryError(code, { effectId, stepKey, message }, cause === undefined ? undefined : { cause }));
|
|
330
|
+
async function client(method, call) {
|
|
331
|
+
try {
|
|
332
|
+
return await call();
|
|
333
|
+
} catch (err) {
|
|
334
|
+
const message = `EffectClient.${method} failed for effect "${effectId}": ${errorMessage(err)}`;
|
|
335
|
+
if (isInvariantError(err))
|
|
336
|
+
throw fail("EFFECT_RUNTIME_INVARIANT", message, err);
|
|
337
|
+
throw fail("EFFECT_CLIENT_UNAVAILABLE", message, err);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const malformedClient = (method) => fail("EFFECT_RUNTIME_INVARIANT", `EffectClient.${method} returned a malformed response for effect "${effectId}"`);
|
|
341
|
+
async function suspend() {
|
|
342
|
+
await client("watch", () => effects.watch({ effectId, executionId, stepKey }));
|
|
343
|
+
return { status: "suspended", wakeAt: clock() + retryIn };
|
|
344
|
+
}
|
|
345
|
+
async function settle(result) {
|
|
346
|
+
const encoding = durableValueCodec3.encode(result);
|
|
347
|
+
if (encoding.status === "invalid") {
|
|
348
|
+
throw fail("EFFECT_HANDLER_INVALID", `Effect "${effectId}" result is not a durable value: ${encoding.message}`);
|
|
349
|
+
}
|
|
350
|
+
const settled = await client("settle", () => effects.settle({ operation, effectId, result }));
|
|
351
|
+
if (!isObject(settled) || !("result" in settled))
|
|
352
|
+
throw malformedClient("settle");
|
|
353
|
+
if (settled.status === "conflict") {
|
|
354
|
+
logger.warn({
|
|
355
|
+
executionId,
|
|
356
|
+
stepKey,
|
|
357
|
+
effectId,
|
|
358
|
+
result: settled.result,
|
|
359
|
+
conflictingResult: settled.conflictingResult
|
|
360
|
+
}, `Effect "${effectId}" settled with a different result; the first result stands`);
|
|
361
|
+
} else if (settled.status !== "settled") {
|
|
362
|
+
throw malformedClient("settle");
|
|
363
|
+
}
|
|
364
|
+
return { status: "settled", result: settled.result };
|
|
365
|
+
}
|
|
366
|
+
const begun = await client("begin", () => effects.begin({ operation, effectId, input, executionId, stepKey }));
|
|
367
|
+
if (!isObject(begun))
|
|
368
|
+
throw malformedClient("begin");
|
|
369
|
+
let next;
|
|
370
|
+
switch (begun.status) {
|
|
371
|
+
case "settled":
|
|
372
|
+
if (!("result" in begun))
|
|
373
|
+
throw malformedClient("begin");
|
|
374
|
+
return { status: "settled", result: begun.result };
|
|
375
|
+
case "rejected":
|
|
376
|
+
if (begun.code !== "EFFECT_REQUEST_MISMATCH")
|
|
377
|
+
throw malformedClient("begin");
|
|
378
|
+
return { status: "rejected", code: begun.code };
|
|
379
|
+
case "new":
|
|
380
|
+
next = "perform";
|
|
381
|
+
break;
|
|
382
|
+
case "pending":
|
|
383
|
+
next = "lookup";
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
throw malformedClient("begin");
|
|
387
|
+
}
|
|
388
|
+
if (next === "lookup") {
|
|
389
|
+
assertLease();
|
|
390
|
+
let found;
|
|
391
|
+
try {
|
|
392
|
+
found = await request.lookup(effectId);
|
|
393
|
+
} catch {
|
|
394
|
+
return suspend();
|
|
395
|
+
}
|
|
396
|
+
if (!isObject(found))
|
|
397
|
+
throw malformedLookup(fail, effectId);
|
|
398
|
+
switch (found.status) {
|
|
399
|
+
case "settled":
|
|
400
|
+
if (!("result" in found))
|
|
401
|
+
throw malformedLookup(fail, effectId);
|
|
402
|
+
return settle(found.result);
|
|
403
|
+
case "unknown":
|
|
404
|
+
return suspend();
|
|
405
|
+
case "not-found":
|
|
406
|
+
break;
|
|
407
|
+
default:
|
|
408
|
+
throw malformedLookup(fail, effectId);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
assertLease();
|
|
412
|
+
let performed;
|
|
413
|
+
try {
|
|
414
|
+
performed = await request.perform(effectId);
|
|
415
|
+
} catch {
|
|
416
|
+
return suspend();
|
|
417
|
+
}
|
|
418
|
+
return settle(performed);
|
|
419
|
+
}
|
|
420
|
+
function malformedLookup(fail, effectId) {
|
|
421
|
+
return fail("EFFECT_HANDLER_INVALID", `lookup for effect "${effectId}" returned a malformed response: expected { status: "settled", result }, { status: "not-found" } or { status: "unknown" }`);
|
|
422
|
+
}
|
|
423
|
+
function isInvariantError(err) {
|
|
424
|
+
return err instanceof EffectClientInvariantError || typeof err === "object" && err !== null && err.code === "EFFECT_RUNTIME_INVARIANT";
|
|
425
|
+
}
|
|
426
|
+
function isObject(value) {
|
|
427
|
+
return typeof value === "object" && value !== null;
|
|
428
|
+
}
|
|
429
|
+
function errorMessage(err) {
|
|
430
|
+
return err instanceof Error ? err.message : String(err);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/internal/step-runner.ts
|
|
434
|
+
function createStepRunner(params) {
|
|
435
|
+
const { event, clock } = params;
|
|
436
|
+
function store(definition, params) {
|
|
437
|
+
return createWorkflowStore({
|
|
438
|
+
definition,
|
|
439
|
+
id: params?.id ?? event.executionId,
|
|
440
|
+
event
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function start(stepKey, target) {
|
|
444
|
+
if (stepKey === "") {
|
|
445
|
+
throw new Error("step.start requires a non-empty step key");
|
|
446
|
+
}
|
|
447
|
+
const request = {
|
|
448
|
+
requestId: { executionId: event.executionId, stepKey },
|
|
449
|
+
workflowId: target.workflowId
|
|
450
|
+
};
|
|
451
|
+
if (target.params !== undefined) {
|
|
452
|
+
request.params = target.params;
|
|
453
|
+
}
|
|
454
|
+
return startStep(stepKey, request);
|
|
455
|
+
}
|
|
456
|
+
function effect(stepKey, descriptor) {
|
|
457
|
+
if (typeof stepKey !== "string" || stepKey === "") {
|
|
458
|
+
throw new Error("step.effect requires a non-empty step key");
|
|
459
|
+
}
|
|
460
|
+
return effectStep(stepKey, descriptor);
|
|
461
|
+
}
|
|
462
|
+
return { run, delay: createDelay(clock), poll, store, start, effect };
|
|
463
|
+
}
|
|
464
|
+
async function* effectStep(key, descriptor) {
|
|
465
|
+
yield new StepKey(key);
|
|
466
|
+
const cached = yield new StepCacheCheck;
|
|
467
|
+
if (cached)
|
|
468
|
+
return yield* replay(cached);
|
|
469
|
+
const { effectId, operation, input, retryIn, lookup, perform } = descriptor ?? {};
|
|
470
|
+
const invalid = invalidEffectRequest({
|
|
471
|
+
effectId,
|
|
472
|
+
operation,
|
|
473
|
+
input,
|
|
474
|
+
retryIn,
|
|
475
|
+
lookup,
|
|
476
|
+
perform
|
|
477
|
+
});
|
|
478
|
+
if (invalid) {
|
|
479
|
+
const err = new WorkflowEffectError("INVALID_EFFECT_REQUEST", `step.effect "${key}": ${invalid}`);
|
|
480
|
+
yield new StepError2(err);
|
|
481
|
+
throw err;
|
|
482
|
+
}
|
|
483
|
+
const outcome = yield new StepEffectCall({
|
|
484
|
+
stepKey: key,
|
|
485
|
+
effectId,
|
|
486
|
+
operation,
|
|
487
|
+
input,
|
|
488
|
+
retryIn,
|
|
489
|
+
lookup: (id) => lookup.call(descriptor, id),
|
|
490
|
+
perform: (id) => perform.call(descriptor, id)
|
|
491
|
+
});
|
|
492
|
+
switch (outcome.status) {
|
|
493
|
+
case "settled":
|
|
494
|
+
return yield* record(outcome.result);
|
|
495
|
+
case "rejected": {
|
|
496
|
+
const err = new WorkflowEffectError(outcome.code, `Effect "${effectId}" was begun with a different operation or input`);
|
|
184
497
|
yield new StepError2(err);
|
|
498
|
+
throw err;
|
|
185
499
|
}
|
|
500
|
+
case "suspended":
|
|
501
|
+
yield new StepEffectWait2(outcome.wakeAt);
|
|
502
|
+
throw new Error("Effect wait yielded control unexpectedly");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function invalidEffectRequest(request) {
|
|
506
|
+
const validated = validateEffectRequest(request);
|
|
507
|
+
if (!validated.ok)
|
|
508
|
+
return validated.message;
|
|
509
|
+
const { retryIn } = request;
|
|
510
|
+
if (typeof retryIn !== "number" || !Number.isFinite(retryIn) || retryIn < 0) {
|
|
511
|
+
return "retryIn must be a finite, non-negative number";
|
|
512
|
+
}
|
|
513
|
+
if (typeof request.lookup !== "function")
|
|
514
|
+
return "lookup must be a function";
|
|
515
|
+
if (typeof request.perform !== "function")
|
|
516
|
+
return "perform must be a function";
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
async function* startStep(key, request) {
|
|
520
|
+
yield new StepKey(key);
|
|
521
|
+
const cached = yield new StepCacheCheck;
|
|
522
|
+
if (cached)
|
|
523
|
+
return yield* replay(cached);
|
|
524
|
+
const started = yield new StepRuntimeCall(async ({ executions }) => {
|
|
525
|
+
try {
|
|
526
|
+
return await executions.start(request);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
throw InfrastructureError3.is(err) ? err : new InfrastructureError3(err);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
if (!started.ok) {
|
|
532
|
+
throw new Error("ExecutionClient.start failed without an infrastructure error");
|
|
533
|
+
}
|
|
534
|
+
const outcome = started.value;
|
|
535
|
+
if (outcome.status === "rejected") {
|
|
536
|
+
const err = new WorkflowStartError(outcome.code, outcome.message);
|
|
537
|
+
yield new StepError2(err);
|
|
186
538
|
throw err;
|
|
187
539
|
}
|
|
540
|
+
return yield* record({ executionId: outcome.executionId });
|
|
188
541
|
}
|
|
189
|
-
|
|
542
|
+
function createWorkflowStore(params) {
|
|
543
|
+
const { id, event } = params;
|
|
544
|
+
const key = { storeName: params.definition.name, storeId: id };
|
|
545
|
+
const definition = {
|
|
546
|
+
...params.definition,
|
|
547
|
+
initial: workflowCallback(params.definition.initial)
|
|
548
|
+
};
|
|
549
|
+
return {
|
|
550
|
+
definition: params.definition,
|
|
551
|
+
id,
|
|
552
|
+
key,
|
|
553
|
+
get(stepKey) {
|
|
554
|
+
return runtimeStep(stepKey ?? getCallSiteHash(this.get), async ({ stores }) => await stores.getStore({ definition, id }));
|
|
555
|
+
},
|
|
556
|
+
select(stepKey, selector) {
|
|
557
|
+
return runtimeStep(stepKey, async ({ stores }) => {
|
|
558
|
+
const snapshot = await stores.getStore({ definition, id });
|
|
559
|
+
return workflowCallback(selector)(cloneStoreState(snapshot.state));
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
update(stepKey, updater) {
|
|
563
|
+
return runtimeStep(stepKey, async ({ stores }) => await stores.updateStore({
|
|
564
|
+
definition,
|
|
565
|
+
id,
|
|
566
|
+
updater: workflowCallback(updater, assertSynchronousUpdater),
|
|
567
|
+
stepId: { executionId: event.executionId, stepKey }
|
|
568
|
+
}));
|
|
569
|
+
},
|
|
570
|
+
updateFrom(stepKey, snapshot, updater) {
|
|
571
|
+
return runtimeStep(stepKey, async ({ stores }) => await stores.updateStoreFrom({
|
|
572
|
+
definition,
|
|
573
|
+
id,
|
|
574
|
+
snapshot,
|
|
575
|
+
updater: workflowCallback(updater, assertSynchronousUpdater),
|
|
576
|
+
stepId: { executionId: event.executionId, stepKey }
|
|
577
|
+
}));
|
|
578
|
+
},
|
|
579
|
+
deleteFrom(stepKey, snapshot) {
|
|
580
|
+
return runtimeStep(stepKey, ({ stores }) => stores.deleteStoreFrom({
|
|
581
|
+
definition,
|
|
582
|
+
id,
|
|
583
|
+
snapshot,
|
|
584
|
+
stepId: { executionId: event.executionId, stepKey }
|
|
585
|
+
}));
|
|
586
|
+
},
|
|
587
|
+
when(arg1, arg2) {
|
|
588
|
+
const stepKey = typeof arg1 === "string" ? arg1 : getCallSiteHash(this.when);
|
|
589
|
+
const selector = typeof arg1 === "string" ? arg2 : arg1;
|
|
590
|
+
return whenStep({
|
|
591
|
+
definition,
|
|
592
|
+
id,
|
|
593
|
+
event,
|
|
594
|
+
stepKey,
|
|
595
|
+
selector
|
|
596
|
+
});
|
|
597
|
+
},
|
|
598
|
+
take(stepKey, selector, claim) {
|
|
599
|
+
return takeStep({
|
|
600
|
+
definition,
|
|
601
|
+
id,
|
|
602
|
+
event,
|
|
603
|
+
stepKey,
|
|
604
|
+
selector,
|
|
605
|
+
claim
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function run(arg1, arg2, arg3) {
|
|
190
611
|
let key = null;
|
|
191
|
-
let
|
|
612
|
+
let fn;
|
|
613
|
+
let options;
|
|
192
614
|
if (typeof arg1 === "string") {
|
|
193
615
|
key = arg1;
|
|
194
|
-
|
|
616
|
+
fn = arg2;
|
|
617
|
+
options = arg3;
|
|
195
618
|
} else {
|
|
196
|
-
|
|
619
|
+
fn = arg1;
|
|
620
|
+
options = arg2;
|
|
197
621
|
}
|
|
198
622
|
if (!key)
|
|
199
|
-
key = getCallSiteHash(
|
|
623
|
+
key = getCallSiteHash(run);
|
|
624
|
+
return durableStep(key, fn, options?.schema);
|
|
625
|
+
}
|
|
626
|
+
function createDelay(clock) {
|
|
627
|
+
function delay(arg1, arg2) {
|
|
628
|
+
let key = null;
|
|
629
|
+
let retryInterval;
|
|
630
|
+
if (typeof arg1 === "string") {
|
|
631
|
+
key = arg1;
|
|
632
|
+
retryInterval = arg2;
|
|
633
|
+
} else {
|
|
634
|
+
retryInterval = arg1;
|
|
635
|
+
}
|
|
636
|
+
if (!key)
|
|
637
|
+
key = getCallSiteHash(delay);
|
|
638
|
+
return delayStep(key, retryInterval, clock);
|
|
639
|
+
}
|
|
640
|
+
return delay;
|
|
641
|
+
}
|
|
642
|
+
async function* delayStep(key, retryInterval, clock) {
|
|
200
643
|
yield new StepKey(key);
|
|
201
644
|
const cached = yield new StepCacheCheck;
|
|
202
645
|
if (cached) {
|
|
203
|
-
yield new StepDelay2(cached.
|
|
646
|
+
yield new StepDelay2(cached.wakeAt);
|
|
204
647
|
} else {
|
|
205
|
-
yield new StepDelay2(
|
|
648
|
+
yield new StepDelay2(clock() + retryInterval);
|
|
206
649
|
}
|
|
207
650
|
}
|
|
208
|
-
|
|
651
|
+
function poll(arg1, arg2, arg3) {
|
|
209
652
|
let key = null;
|
|
210
653
|
let opts;
|
|
211
654
|
let predicate;
|
|
@@ -227,26 +670,185 @@ async function* poll(arg1, arg2, arg3) {
|
|
|
227
670
|
});
|
|
228
671
|
}
|
|
229
672
|
};
|
|
230
|
-
|
|
673
|
+
return run(key, task);
|
|
674
|
+
}
|
|
675
|
+
async function* durableStep(key, fn, schema) {
|
|
676
|
+
yield new StepKey(key);
|
|
677
|
+
const cached = yield new StepCacheCheck;
|
|
678
|
+
if (cached) {
|
|
679
|
+
const value = yield* replay(cached);
|
|
680
|
+
if (!schema)
|
|
681
|
+
return value;
|
|
682
|
+
const validated = await validateStepValue(key, schema, value);
|
|
683
|
+
if (cached.schema === "rejected" && validated.ok) {
|
|
684
|
+
throw new WorkflowValueError3("STEP_SCHEMA_INVALID", key, `Step "${key}" schema rejected its value on the first run`);
|
|
685
|
+
}
|
|
686
|
+
return unwrapValidated(validated);
|
|
687
|
+
}
|
|
688
|
+
let result;
|
|
689
|
+
try {
|
|
690
|
+
result = await fn();
|
|
691
|
+
} catch (err) {
|
|
692
|
+
if (err instanceof RetryableError) {
|
|
693
|
+
yield new StepError2(err, {
|
|
694
|
+
maxAttempts: err.maxAttempts,
|
|
695
|
+
retryInterval: err.retryInterval
|
|
696
|
+
});
|
|
697
|
+
} else {
|
|
698
|
+
yield new StepError2(err);
|
|
699
|
+
}
|
|
700
|
+
throw err;
|
|
701
|
+
}
|
|
702
|
+
if (!schema)
|
|
703
|
+
return yield* record(result);
|
|
704
|
+
const encoding = durableValueCodec4.encode(result);
|
|
705
|
+
if (encoding.status === "invalid")
|
|
706
|
+
return yield* record(result);
|
|
707
|
+
const decoding = durableValueCodec4.decode(encoding.canonical);
|
|
708
|
+
if (decoding.status === "invalid")
|
|
709
|
+
throw new Error(decoding.message);
|
|
710
|
+
const validated = await validateStepValue(key, schema, decoding.value);
|
|
711
|
+
yield* record(result, validated.ok ? "accepted" : "rejected");
|
|
712
|
+
return unwrapValidated(validated);
|
|
713
|
+
}
|
|
714
|
+
async function validateStepValue(key, schema, value) {
|
|
715
|
+
const result = await schema["~standard"].validate(value);
|
|
716
|
+
if (result.issues) {
|
|
717
|
+
const messages = result.issues.map((issue) => issue.message).join("; ");
|
|
718
|
+
return {
|
|
719
|
+
ok: false,
|
|
720
|
+
error: new WorkflowValueError3("STEP_SCHEMA_INVALID", key, `Step "${key}" value failed its schema: ${messages}`)
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
return { ok: true, value: result.value };
|
|
724
|
+
}
|
|
725
|
+
function unwrapValidated(validated) {
|
|
726
|
+
if (!validated.ok)
|
|
727
|
+
throw validated.error;
|
|
728
|
+
return validated.value;
|
|
729
|
+
}
|
|
730
|
+
async function* record(result, schema) {
|
|
731
|
+
const recorded = yield new StepResult2(result, schema);
|
|
732
|
+
return recorded.result;
|
|
733
|
+
}
|
|
734
|
+
async function* replay(cached) {
|
|
735
|
+
const recorded = yield cached;
|
|
736
|
+
return recorded.result;
|
|
737
|
+
}
|
|
738
|
+
async function* fail(key, error) {
|
|
739
|
+
const err = error instanceof WorkflowValueError3 && error.stepKey !== key ? new WorkflowValueError3(error.code, key, error.message) : error;
|
|
740
|
+
yield new StepError2(err);
|
|
741
|
+
throw err;
|
|
742
|
+
}
|
|
743
|
+
async function* runtimeStep(key, call) {
|
|
744
|
+
yield new StepKey(key);
|
|
745
|
+
const cached = yield new StepCacheCheck;
|
|
746
|
+
if (cached)
|
|
747
|
+
return yield* replay(cached);
|
|
748
|
+
const outcome = yield new StepRuntimeCall(call);
|
|
749
|
+
if (!outcome.ok)
|
|
750
|
+
return yield* fail(key, outcome.error);
|
|
751
|
+
return yield* record(outcome.value);
|
|
752
|
+
}
|
|
753
|
+
async function* whenStep(params) {
|
|
754
|
+
const { definition, id, event, stepKey, selector } = params;
|
|
755
|
+
yield new StepKey(stepKey);
|
|
756
|
+
const cached = yield new StepCacheCheck;
|
|
757
|
+
if (cached)
|
|
758
|
+
return yield* replay(cached);
|
|
759
|
+
const read = yield new StepRuntimeCall(({ stores }) => stores.getStore({ definition, id }));
|
|
760
|
+
if (!read.ok)
|
|
761
|
+
return yield* fail(stepKey, read.error);
|
|
762
|
+
const snapshot = read.value;
|
|
763
|
+
const { result, readPaths } = trackStoreSelector(cloneStoreState(snapshot.state), selector);
|
|
764
|
+
if (result !== undefined && result !== null && result !== false) {
|
|
765
|
+
return yield* record(result);
|
|
766
|
+
}
|
|
767
|
+
yield* registerWaiter({
|
|
768
|
+
workflowId: event.workflowId,
|
|
769
|
+
executionId: event.executionId,
|
|
770
|
+
stepKey,
|
|
771
|
+
storeName: definition.name,
|
|
772
|
+
storeId: id,
|
|
773
|
+
instanceId: snapshot.instanceId,
|
|
774
|
+
sinceVersion: snapshot.version,
|
|
775
|
+
readPaths
|
|
776
|
+
});
|
|
777
|
+
throw new Error("Store wait yielded control unexpectedly");
|
|
778
|
+
}
|
|
779
|
+
async function* takeStep(params) {
|
|
780
|
+
const { definition, id, event, stepKey, selector, claim } = params;
|
|
781
|
+
yield new StepKey(stepKey);
|
|
782
|
+
const cached = yield new StepCacheCheck;
|
|
783
|
+
if (cached)
|
|
784
|
+
return yield* replay(cached);
|
|
785
|
+
const taken = yield new StepRuntimeCall(({ stores }) => stores.takeFromStore({
|
|
786
|
+
definition,
|
|
787
|
+
id,
|
|
788
|
+
selector: workflowCallback(selector),
|
|
789
|
+
claim: workflowCallback(claim, assertSynchronousClaim),
|
|
790
|
+
stepId: { executionId: event.executionId, stepKey }
|
|
791
|
+
}));
|
|
792
|
+
if (!taken.ok)
|
|
793
|
+
return yield* fail(stepKey, taken.error);
|
|
794
|
+
const outcome = taken.value;
|
|
795
|
+
if (outcome.matched)
|
|
796
|
+
return yield* record(outcome.selected);
|
|
797
|
+
yield* registerWaiter({
|
|
798
|
+
workflowId: event.workflowId,
|
|
799
|
+
executionId: event.executionId,
|
|
800
|
+
stepKey,
|
|
801
|
+
storeName: definition.name,
|
|
802
|
+
storeId: id,
|
|
803
|
+
instanceId: outcome.instanceId,
|
|
804
|
+
sinceVersion: outcome.version,
|
|
805
|
+
readPaths: outcome.readPaths
|
|
806
|
+
});
|
|
807
|
+
throw new Error("Store take yielded control unexpectedly");
|
|
808
|
+
}
|
|
809
|
+
async function* registerWaiter(waiter) {
|
|
810
|
+
const registered = yield new StepRuntimeCall(({ stores }) => stores.registerWaiter(waiter));
|
|
811
|
+
if (!registered.ok)
|
|
812
|
+
return yield* fail(waiter.stepKey, registered.error);
|
|
813
|
+
yield new StepStoreWait2;
|
|
231
814
|
}
|
|
232
815
|
|
|
233
816
|
// src/exports/workflow.ts
|
|
234
817
|
import {
|
|
235
|
-
StepResponse as
|
|
818
|
+
StepResponse as StepResponse5,
|
|
236
819
|
StepKey as StepKey2,
|
|
237
820
|
StepError as StepError3,
|
|
238
821
|
StepResult as StepResult3,
|
|
239
822
|
StepDelay as StepDelay3,
|
|
240
823
|
StepCacheCheck as StepCacheCheck2,
|
|
241
|
-
|
|
242
|
-
|
|
824
|
+
StepEffectWait as StepEffectWait3,
|
|
825
|
+
StepInvalid as StepInvalid2,
|
|
826
|
+
StepStoreWait as StepStoreWait3,
|
|
827
|
+
WorkflowResult as WorkflowResult2,
|
|
828
|
+
InfrastructureError as InfrastructureError4,
|
|
829
|
+
LeaseLostError,
|
|
830
|
+
StepConflictError,
|
|
831
|
+
isLeaseLost,
|
|
832
|
+
WORKFLOW_RESULT_STEP_KEY
|
|
243
833
|
} from "@yieldstar/core";
|
|
244
834
|
function workflow(workflowFn) {
|
|
245
|
-
return async function* workflowGenerator({
|
|
835
|
+
return async function* workflowGenerator({
|
|
836
|
+
event,
|
|
837
|
+
lease,
|
|
838
|
+
signal,
|
|
839
|
+
heapClient,
|
|
840
|
+
storeClient,
|
|
841
|
+
executionClient,
|
|
842
|
+
effectClient,
|
|
843
|
+
clock,
|
|
844
|
+
logger
|
|
845
|
+
}) {
|
|
846
|
+
const stepRunner = createStepRunner({ event, clock });
|
|
246
847
|
const workflowIterator = workflowFn(stepRunner, event, logger);
|
|
247
848
|
const { executionId } = event;
|
|
248
849
|
let iteratorResult = null;
|
|
249
850
|
let nextIteratorResult = null;
|
|
851
|
+
let resumeWith;
|
|
250
852
|
const stepKeys = new Set;
|
|
251
853
|
while (true) {
|
|
252
854
|
let stepAttempt = 0;
|
|
@@ -255,10 +857,12 @@ function workflow(workflowFn) {
|
|
|
255
857
|
iteratorResult = nextIteratorResult;
|
|
256
858
|
nextIteratorResult = null;
|
|
257
859
|
} else {
|
|
258
|
-
|
|
860
|
+
stopIfAborted(signal);
|
|
861
|
+
iteratorResult = await workflowIterator.next(resumeWith);
|
|
259
862
|
}
|
|
863
|
+
resumeWith = undefined;
|
|
260
864
|
if (iteratorResult.done) {
|
|
261
|
-
stepKey =
|
|
865
|
+
stepKey = WORKFLOW_RESULT_STEP_KEY;
|
|
262
866
|
} else if (!(iteratorResult.value instanceof StepKey2)) {
|
|
263
867
|
throw new Error("Step runners must yield a StepKey object");
|
|
264
868
|
} else {
|
|
@@ -268,10 +872,10 @@ function workflow(workflowFn) {
|
|
|
268
872
|
}
|
|
269
873
|
stepKeys.add(stepKey);
|
|
270
874
|
}
|
|
271
|
-
let cached = await heapClient.readStep({
|
|
875
|
+
let cached = await infrastructure(() => heapClient.readStep({
|
|
272
876
|
executionId,
|
|
273
877
|
stepKey
|
|
274
|
-
});
|
|
878
|
+
}));
|
|
275
879
|
if (cached?.meta) {
|
|
276
880
|
stepAttempt = cached.meta.attempt + 1;
|
|
277
881
|
}
|
|
@@ -288,62 +892,112 @@ function workflow(workflowFn) {
|
|
|
288
892
|
stepResponse = iteratorResult.value;
|
|
289
893
|
}
|
|
290
894
|
if (stepResponse instanceof StepCacheCheck2) {
|
|
291
|
-
const cachedResponse = cached ?
|
|
895
|
+
const cachedResponse = cached ? decodeStepRecord(cached.stepResponseJson) : null;
|
|
896
|
+
stopIfAborted(signal);
|
|
292
897
|
iteratorResult = await workflowIterator.next(cachedResponse);
|
|
293
898
|
stepResponse = iteratorResult.value;
|
|
899
|
+
while (stepResponse instanceof StepRuntimeCall || stepResponse instanceof StepEffectCall) {
|
|
900
|
+
stopIfAborted(signal);
|
|
901
|
+
const callResult = stepResponse instanceof StepEffectCall ? await executeEffectCall(stepResponse.request, {
|
|
902
|
+
effects: effectClient,
|
|
903
|
+
executionId,
|
|
904
|
+
clock,
|
|
905
|
+
assertLease: () => stopIfAborted(signal),
|
|
906
|
+
logger
|
|
907
|
+
}) : await executeRuntimeCall(stepResponse, {
|
|
908
|
+
stores: storeClient,
|
|
909
|
+
executions: executionClient
|
|
910
|
+
});
|
|
911
|
+
iteratorResult = await workflowIterator.next(callResult);
|
|
912
|
+
stepResponse = iteratorResult.value;
|
|
913
|
+
}
|
|
294
914
|
} else if (!(stepResponse instanceof WorkflowResult2)) {
|
|
295
915
|
throw new Error("Step runners must yield a CacheCheck object");
|
|
296
916
|
}
|
|
297
917
|
if (isIterable(stepResponse)) {
|
|
298
918
|
console.log(`[ERR] Steps must be yielded using yield*
|
|
299
919
|
`);
|
|
300
|
-
stepResponse = new
|
|
301
|
-
} else if (!(stepResponse instanceof
|
|
920
|
+
stepResponse = new StepInvalid2;
|
|
921
|
+
} else if (!(stepResponse instanceof StepResponse5)) {
|
|
302
922
|
console.log(`[ERR] Iterators should yield a StepResponse. Got ${JSON.stringify(stepResponse)}`);
|
|
303
|
-
stepResponse = new
|
|
923
|
+
stepResponse = new StepInvalid2;
|
|
924
|
+
}
|
|
925
|
+
let stepResponseJson;
|
|
926
|
+
if (!cached || stepResponse instanceof WorkflowResult2) {
|
|
927
|
+
const record = encodeStepRecord(stepResponse, stepKey);
|
|
928
|
+
stepResponseJson = record.json;
|
|
929
|
+
stepResponse = record.response;
|
|
304
930
|
}
|
|
305
931
|
const needsRetry = !cached?.meta.done && stepResponse instanceof StepError3 && stepResponse.maxAttempts > stepAttempt + 1;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
932
|
+
const needsWake = !cached?.meta.done && (stepResponse instanceof StepStoreWait3 || stepResponse instanceof StepEffectWait3);
|
|
933
|
+
if (!cached && stepResponseJson !== undefined) {
|
|
934
|
+
const done = !needsRetry && !needsWake;
|
|
935
|
+
stopIfAborted(signal);
|
|
936
|
+
const written = await infrastructure(() => heapClient.writeStep({
|
|
937
|
+
lease,
|
|
309
938
|
stepKey,
|
|
310
939
|
stepAttempt,
|
|
311
|
-
|
|
312
|
-
stepResponseJson
|
|
313
|
-
});
|
|
940
|
+
done,
|
|
941
|
+
stepResponseJson
|
|
942
|
+
}));
|
|
943
|
+
if (isLeaseLost(written)) {
|
|
944
|
+
throw new InfrastructureError4(new LeaseLostError(executionId));
|
|
945
|
+
}
|
|
946
|
+
if (written.stepResponseJson !== stepResponseJson || written.meta.done !== done) {
|
|
947
|
+
throw new InfrastructureError4(new StepConflictError(executionId, stepKey));
|
|
948
|
+
}
|
|
314
949
|
}
|
|
315
950
|
if (stepResponse instanceof WorkflowResult2) {
|
|
316
951
|
return stepResponse;
|
|
317
952
|
}
|
|
318
|
-
if (stepResponse instanceof
|
|
953
|
+
if (iteratorResult.done && stepResponse instanceof StepError3) {
|
|
954
|
+
throw stepResponse.err;
|
|
955
|
+
}
|
|
956
|
+
if (stepResponse instanceof StepInvalid2) {
|
|
319
957
|
break;
|
|
320
958
|
}
|
|
321
959
|
if (stepResponse instanceof StepError3) {
|
|
322
960
|
if (needsRetry) {
|
|
323
|
-
yield new StepDelay3(
|
|
961
|
+
yield new StepDelay3(clock() + stepResponse.retryInterval);
|
|
324
962
|
} else {
|
|
963
|
+
stopIfAborted(signal);
|
|
325
964
|
nextIteratorResult = await workflowIterator.throw(stepResponse.err);
|
|
326
965
|
continue;
|
|
327
966
|
}
|
|
328
967
|
}
|
|
329
968
|
if (stepResponse instanceof StepDelay3) {
|
|
330
|
-
if (stepResponse.
|
|
969
|
+
if (stepResponse.wakeAt <= clock()) {
|
|
331
970
|
continue;
|
|
332
971
|
}
|
|
333
972
|
}
|
|
334
|
-
if (
|
|
973
|
+
if (stepResponse instanceof StepResult3) {
|
|
974
|
+
resumeWith = stepResponse;
|
|
975
|
+
} else {
|
|
335
976
|
yield stepResponse;
|
|
336
977
|
}
|
|
337
978
|
}
|
|
338
|
-
throw new Error("
|
|
979
|
+
throw new Error("Workflow yielded an invalid step. Steps must be yielded with yield* from a step runner.");
|
|
339
980
|
};
|
|
340
981
|
}
|
|
341
982
|
var createWorkflow = workflow;
|
|
983
|
+
function stopIfAborted(signal) {
|
|
984
|
+
if (signal.aborted)
|
|
985
|
+
throw new InfrastructureError4(signal.reason);
|
|
986
|
+
}
|
|
342
987
|
export {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
988
|
+
EffectClientInvariantError2 as EffectClientInvariantError,
|
|
989
|
+
EffectDeliveryError2 as EffectDeliveryError,
|
|
990
|
+
ExecutionAdmissionError3 as ExecutionAdmissionError,
|
|
991
|
+
RetryableError,
|
|
992
|
+
WORKFLOW_RESULT_STEP_KEY2 as WORKFLOW_RESULT_STEP_KEY,
|
|
993
|
+
WorkflowEffectError2 as WorkflowEffectError,
|
|
994
|
+
WorkflowStartError2 as WorkflowStartError,
|
|
995
|
+
WorkflowValueError4 as WorkflowValueError,
|
|
347
996
|
createHttpSdkFactory,
|
|
348
|
-
|
|
997
|
+
createLocalSdk,
|
|
998
|
+
createWorkflow,
|
|
999
|
+
createWorkflowRouter,
|
|
1000
|
+
defineStore,
|
|
1001
|
+
registerErrorClass2 as registerErrorClass,
|
|
1002
|
+
workflow
|
|
349
1003
|
};
|