yieldstar 0.5.0 → 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 +43 -52
- 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 +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +664 -214
- 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 +77 -14
- package/dist/internal/step-runner.d.ts.map +1 -1
- package/package.json +2 -3
- 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 +157 -101
- package/src/index.ts +34 -2
- 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 +515 -191
- package/src/internal/serialise.ts +0 -47
- package/tsconfig.tsbuildinfo +0 -1
package/dist/index.js
CHANGED
|
@@ -1,56 +1,83 @@
|
|
|
1
1
|
// src/exports/sdk-local.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
ExecutionAdmissionError,
|
|
4
|
+
waitForExecutionOutcome
|
|
5
|
+
} from "@yieldstar/core";
|
|
6
|
+
function createLocalSdk(runtime, options) {
|
|
7
|
+
const { executions } = runtime;
|
|
4
8
|
return {
|
|
5
9
|
async trigger(event) {
|
|
6
|
-
const
|
|
7
|
-
executionId: event.executionId
|
|
10
|
+
const admission = await executions.admit({
|
|
11
|
+
executionId: event.executionId,
|
|
8
12
|
workflowId: event.workflowId,
|
|
9
|
-
params: event.params
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 };
|
|
13
20
|
},
|
|
14
21
|
async triggerAndWait(event) {
|
|
15
|
-
const executionId =
|
|
16
|
-
const
|
|
17
|
-
invoker
|
|
22
|
+
const { executionId } = await this.trigger(event);
|
|
23
|
+
const outcome = await waitForExecutionOutcome(executions, executionId, {
|
|
24
|
+
emitter: options?.invoker?.workflowEndEmitter,
|
|
25
|
+
pollInterval: options?.pollInterval
|
|
18
26
|
});
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
|
21
33
|
}
|
|
22
34
|
};
|
|
23
35
|
}
|
|
24
36
|
// src/exports/sdk-http.ts
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
import {
|
|
38
|
+
ExecutionAdmissionError as ExecutionAdmissionError2,
|
|
39
|
+
durableValueCodec
|
|
40
|
+
} from "@yieldstar/core";
|
|
41
|
+
var admissionErrorCodes = [
|
|
42
|
+
"EXECUTION_ID_CONFLICT",
|
|
43
|
+
"INVALID_EXECUTION_EVENT"
|
|
44
|
+
];
|
|
28
45
|
function createHttpSdkFactory() {
|
|
29
46
|
return (params) => {
|
|
30
47
|
return {
|
|
31
48
|
async trigger(event) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
}
|
|
38
60
|
const res = await fetch(`${params.url}/trigger`, {
|
|
39
61
|
...params.fetchOptions,
|
|
40
62
|
method: "POST",
|
|
41
|
-
|
|
63
|
+
headers: { "content-type": "application/json" },
|
|
64
|
+
body: encoding.canonical
|
|
42
65
|
});
|
|
43
66
|
if (res.ok) {
|
|
67
|
+
const ack = await res.json();
|
|
44
68
|
return {
|
|
45
|
-
executionId:
|
|
46
|
-
ack: () =>
|
|
47
|
-
waitForResult: () => this.waitForResult(
|
|
48
|
-
executionId: executionEvent.executionId
|
|
49
|
-
})
|
|
69
|
+
executionId: ack.executionId,
|
|
70
|
+
ack: async () => ack,
|
|
71
|
+
waitForResult: () => this.waitForResult(ack)
|
|
50
72
|
};
|
|
51
|
-
} else {
|
|
52
|
-
throw res.statusText;
|
|
53
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;
|
|
54
81
|
},
|
|
55
82
|
async waitForResult(ack) {
|
|
56
83
|
const res = await fetch(`${params.url}/events`, {
|
|
@@ -58,11 +85,18 @@ function createHttpSdkFactory() {
|
|
|
58
85
|
body: JSON.stringify({ executionId: ack.executionId }),
|
|
59
86
|
...params.fetchOptions
|
|
60
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
|
+
}
|
|
61
95
|
if (res.status === 500) {
|
|
62
|
-
const error =
|
|
63
|
-
throw
|
|
96
|
+
const error = decoding.value instanceof Error ? decoding.value : new Error(String(decoding.value));
|
|
97
|
+
throw error;
|
|
64
98
|
}
|
|
65
|
-
return
|
|
99
|
+
return decoding.value;
|
|
66
100
|
}
|
|
67
101
|
};
|
|
68
102
|
};
|
|
@@ -72,6 +106,8 @@ function createWorkflowRouter(workflows) {
|
|
|
72
106
|
return workflows;
|
|
73
107
|
}
|
|
74
108
|
// src/exports/errors.ts
|
|
109
|
+
import { registerErrorClass } from "@yieldstar/core";
|
|
110
|
+
|
|
75
111
|
class RetryableError extends Error {
|
|
76
112
|
maxAttempts;
|
|
77
113
|
retryInterval;
|
|
@@ -82,6 +118,20 @@ class RetryableError extends Error {
|
|
|
82
118
|
this.retryInterval = opts.retryInterval;
|
|
83
119
|
}
|
|
84
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
|
+
|
|
85
135
|
// src/internal/utils.ts
|
|
86
136
|
import { createHash } from "crypto";
|
|
87
137
|
var isIterable = (i) => {
|
|
@@ -99,146 +149,435 @@ function getCallSiteHash(stepFn) {
|
|
|
99
149
|
return createHash("sha1").update(callSite).digest("hex");
|
|
100
150
|
}
|
|
101
151
|
|
|
102
|
-
// src/internal/
|
|
152
|
+
// src/internal/step-record.ts
|
|
103
153
|
import {
|
|
154
|
+
DurableValueError,
|
|
104
155
|
StepDelay,
|
|
156
|
+
StepEffectWait,
|
|
105
157
|
StepError,
|
|
158
|
+
StepInvalid,
|
|
106
159
|
StepResult,
|
|
107
160
|
StepStoreWait,
|
|
108
|
-
WorkflowResult
|
|
161
|
+
WorkflowResult,
|
|
162
|
+
WorkflowValueError,
|
|
163
|
+
decodeParsedValue,
|
|
164
|
+
durableValueCodec as durableValueCodec2,
|
|
165
|
+
encodeThrownValue
|
|
109
166
|
} from "@yieldstar/core";
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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);
|
|
115
177
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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`);
|
|
119
194
|
}
|
|
120
|
-
function
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
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;
|
|
124
217
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const { err, ...params } = stepResponse;
|
|
131
|
-
return new StepError(stepResponse.err, params);
|
|
132
|
-
case "step-delay":
|
|
133
|
-
return new StepDelay(stepResponse.resumeIn);
|
|
134
|
-
case "step-result":
|
|
135
|
-
return new StepResult(stepResponse.result);
|
|
136
|
-
case "store-wait":
|
|
137
|
-
return new StepStoreWait;
|
|
138
|
-
case "workflow-result":
|
|
139
|
-
return new WorkflowResult(stepResponse.result);
|
|
140
|
-
default:
|
|
141
|
-
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;
|
|
142
223
|
}
|
|
224
|
+
throw new Error(`Unknown step record type: ${record.type}`);
|
|
143
225
|
}
|
|
144
226
|
|
|
145
227
|
// src/internal/step-runner.ts
|
|
146
228
|
import {
|
|
229
|
+
assertSynchronousClaim,
|
|
230
|
+
assertSynchronousUpdater,
|
|
147
231
|
cloneStoreState,
|
|
148
232
|
defineStore,
|
|
233
|
+
InfrastructureError as InfrastructureError3,
|
|
149
234
|
StepKey,
|
|
150
235
|
StepError as StepError2,
|
|
151
236
|
StepResult as StepResult2,
|
|
152
237
|
StepDelay as StepDelay2,
|
|
153
238
|
StepCacheCheck,
|
|
154
239
|
StepStoreWait as StepStoreWait2,
|
|
155
|
-
trackStoreSelector
|
|
240
|
+
trackStoreSelector,
|
|
241
|
+
durableValueCodec as durableValueCodec4,
|
|
242
|
+
StepEffectWait as StepEffectWait2,
|
|
243
|
+
validateEffectRequest,
|
|
244
|
+
WorkflowEffectError,
|
|
245
|
+
WorkflowStartError,
|
|
246
|
+
WorkflowValueError as WorkflowValueError3
|
|
156
247
|
} from "@yieldstar/core";
|
|
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;
|
|
264
|
+
}
|
|
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);
|
|
281
|
+
}
|
|
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);
|
|
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) {
|
|
301
|
+
try {
|
|
302
|
+
return await fn();
|
|
303
|
+
} catch (err) {
|
|
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
|
|
157
434
|
function createStepRunner(params) {
|
|
158
|
-
const { event,
|
|
159
|
-
function store(definition,
|
|
160
|
-
|
|
161
|
-
const key = `store:${definition.name}:${id}`;
|
|
162
|
-
return storeStep({
|
|
163
|
-
key,
|
|
435
|
+
const { event, clock } = params;
|
|
436
|
+
function store(definition, params) {
|
|
437
|
+
return createWorkflowStore({
|
|
164
438
|
definition,
|
|
165
|
-
id,
|
|
166
|
-
event
|
|
167
|
-
storeClient,
|
|
168
|
-
initial: params2?.initial
|
|
439
|
+
id: params?.id ?? event.executionId,
|
|
440
|
+
event
|
|
169
441
|
});
|
|
170
442
|
}
|
|
171
|
-
|
|
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 };
|
|
172
463
|
}
|
|
173
|
-
async function*
|
|
174
|
-
const { key, definition, id, event, storeClient, initial } = params;
|
|
464
|
+
async function* effectStep(key, descriptor) {
|
|
175
465
|
yield new StepKey(key);
|
|
176
466
|
const cached = yield new StepCacheCheck;
|
|
177
|
-
if (cached)
|
|
178
|
-
yield cached;
|
|
179
|
-
|
|
180
|
-
|
|
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`);
|
|
497
|
+
yield new StepError2(err);
|
|
498
|
+
throw err;
|
|
181
499
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
event,
|
|
186
|
-
storeClient
|
|
187
|
-
});
|
|
500
|
+
case "suspended":
|
|
501
|
+
yield new StepEffectWait2(outcome.wakeAt);
|
|
502
|
+
throw new Error("Effect wait yielded control unexpectedly");
|
|
188
503
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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);
|
|
203
537
|
yield new StepError2(err);
|
|
204
538
|
throw err;
|
|
205
539
|
}
|
|
540
|
+
return yield* record({ executionId: outcome.executionId });
|
|
206
541
|
}
|
|
207
542
|
function createWorkflowStore(params) {
|
|
208
|
-
const {
|
|
209
|
-
const key = { storeName: definition.name, storeId: id };
|
|
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
|
+
};
|
|
210
549
|
return {
|
|
211
|
-
definition,
|
|
550
|
+
definition: params.definition,
|
|
212
551
|
id,
|
|
213
552
|
key,
|
|
214
553
|
get(stepKey) {
|
|
215
|
-
return
|
|
554
|
+
return runtimeStep(stepKey ?? getCallSiteHash(this.get), async ({ stores }) => await stores.getStore({ definition, id }));
|
|
216
555
|
},
|
|
217
556
|
select(stepKey, selector) {
|
|
218
|
-
return
|
|
219
|
-
const snapshot = await
|
|
220
|
-
return selector(cloneStoreState(snapshot.state));
|
|
557
|
+
return runtimeStep(stepKey, async ({ stores }) => {
|
|
558
|
+
const snapshot = await stores.getStore({ definition, id });
|
|
559
|
+
return workflowCallback(selector)(cloneStoreState(snapshot.state));
|
|
221
560
|
});
|
|
222
561
|
},
|
|
223
562
|
update(stepKey, updater) {
|
|
224
|
-
return
|
|
563
|
+
return runtimeStep(stepKey, async ({ stores }) => await stores.updateStore({
|
|
225
564
|
definition,
|
|
226
565
|
id,
|
|
227
|
-
updater,
|
|
566
|
+
updater: workflowCallback(updater, assertSynchronousUpdater),
|
|
228
567
|
stepId: { executionId: event.executionId, stepKey }
|
|
229
568
|
}));
|
|
230
569
|
},
|
|
231
570
|
updateFrom(stepKey, snapshot, updater) {
|
|
232
|
-
return
|
|
571
|
+
return runtimeStep(stepKey, async ({ stores }) => await stores.updateStoreFrom({
|
|
233
572
|
definition,
|
|
234
573
|
id,
|
|
235
574
|
snapshot,
|
|
236
|
-
updater,
|
|
575
|
+
updater: workflowCallback(updater, assertSynchronousUpdater),
|
|
237
576
|
stepId: { executionId: event.executionId, stepKey }
|
|
238
577
|
}));
|
|
239
578
|
},
|
|
240
579
|
deleteFrom(stepKey, snapshot) {
|
|
241
|
-
return
|
|
580
|
+
return runtimeStep(stepKey, ({ stores }) => stores.deleteStoreFrom({
|
|
242
581
|
definition,
|
|
243
582
|
id,
|
|
244
583
|
snapshot,
|
|
@@ -252,7 +591,6 @@ function createWorkflowStore(params) {
|
|
|
252
591
|
definition,
|
|
253
592
|
id,
|
|
254
593
|
event,
|
|
255
|
-
storeClient,
|
|
256
594
|
stepKey,
|
|
257
595
|
selector
|
|
258
596
|
});
|
|
@@ -262,7 +600,6 @@ function createWorkflowStore(params) {
|
|
|
262
600
|
definition,
|
|
263
601
|
id,
|
|
264
602
|
event,
|
|
265
|
-
storeClient,
|
|
266
603
|
stepKey,
|
|
267
604
|
selector,
|
|
268
605
|
claim
|
|
@@ -270,39 +607,45 @@ function createWorkflowStore(params) {
|
|
|
270
607
|
}
|
|
271
608
|
};
|
|
272
609
|
}
|
|
273
|
-
function run(arg1, arg2) {
|
|
610
|
+
function run(arg1, arg2, arg3) {
|
|
274
611
|
let key = null;
|
|
275
612
|
let fn;
|
|
613
|
+
let options;
|
|
276
614
|
if (typeof arg1 === "string") {
|
|
277
615
|
key = arg1;
|
|
278
616
|
fn = arg2;
|
|
617
|
+
options = arg3;
|
|
279
618
|
} else {
|
|
280
619
|
fn = arg1;
|
|
620
|
+
options = arg2;
|
|
281
621
|
}
|
|
282
622
|
if (!key)
|
|
283
623
|
key = getCallSiteHash(run);
|
|
284
|
-
return durableStep(key, fn);
|
|
624
|
+
return durableStep(key, fn, options?.schema);
|
|
285
625
|
}
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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);
|
|
294
639
|
}
|
|
295
|
-
|
|
296
|
-
key = getCallSiteHash(delay);
|
|
297
|
-
return delayStep(key, retryInterval);
|
|
640
|
+
return delay;
|
|
298
641
|
}
|
|
299
|
-
async function* delayStep(key, retryInterval) {
|
|
642
|
+
async function* delayStep(key, retryInterval, clock) {
|
|
300
643
|
yield new StepKey(key);
|
|
301
644
|
const cached = yield new StepCacheCheck;
|
|
302
645
|
if (cached) {
|
|
303
|
-
yield new StepDelay2(cached.
|
|
646
|
+
yield new StepDelay2(cached.wakeAt);
|
|
304
647
|
} else {
|
|
305
|
-
yield new StepDelay2(
|
|
648
|
+
yield new StepDelay2(clock() + retryInterval);
|
|
306
649
|
}
|
|
307
650
|
}
|
|
308
651
|
function poll(arg1, arg2, arg3) {
|
|
@@ -329,20 +672,22 @@ function poll(arg1, arg2, arg3) {
|
|
|
329
672
|
};
|
|
330
673
|
return run(key, task);
|
|
331
674
|
}
|
|
332
|
-
async function* durableStep(key, fn) {
|
|
675
|
+
async function* durableStep(key, fn, schema) {
|
|
333
676
|
yield new StepKey(key);
|
|
334
677
|
const cached = yield new StepCacheCheck;
|
|
335
678
|
if (cached) {
|
|
336
|
-
yield cached;
|
|
337
|
-
if (
|
|
338
|
-
|
|
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`);
|
|
339
685
|
}
|
|
340
|
-
return
|
|
686
|
+
return unwrapValidated(validated);
|
|
341
687
|
}
|
|
688
|
+
let result;
|
|
342
689
|
try {
|
|
343
|
-
|
|
344
|
-
yield new StepResult2(result);
|
|
345
|
-
return result;
|
|
690
|
+
result = await fn();
|
|
346
691
|
} catch (err) {
|
|
347
692
|
if (err instanceof RetryableError) {
|
|
348
693
|
yield new StepError2(err, {
|
|
@@ -354,101 +699,156 @@ async function* durableStep(key, fn) {
|
|
|
354
699
|
}
|
|
355
700
|
throw err;
|
|
356
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);
|
|
357
752
|
}
|
|
358
753
|
async function* whenStep(params) {
|
|
359
|
-
const { definition, id, event,
|
|
754
|
+
const { definition, id, event, stepKey, selector } = params;
|
|
360
755
|
yield new StepKey(stepKey);
|
|
361
756
|
const cached = yield new StepCacheCheck;
|
|
362
|
-
if (cached)
|
|
363
|
-
yield cached;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
const snapshot = await storeClient.getStore({ definition, id });
|
|
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;
|
|
370
763
|
const { result, readPaths } = trackStoreSelector(cloneStoreState(snapshot.state), selector);
|
|
371
764
|
if (result !== undefined && result !== null && result !== false) {
|
|
372
|
-
|
|
373
|
-
yield new StepResult2(clonedResult);
|
|
374
|
-
return clonedResult;
|
|
765
|
+
return yield* record(result);
|
|
375
766
|
}
|
|
376
|
-
|
|
767
|
+
yield* registerWaiter({
|
|
377
768
|
workflowId: event.workflowId,
|
|
378
769
|
executionId: event.executionId,
|
|
379
770
|
stepKey,
|
|
380
|
-
event,
|
|
381
771
|
storeName: definition.name,
|
|
382
772
|
storeId: id,
|
|
383
773
|
instanceId: snapshot.instanceId,
|
|
384
774
|
sinceVersion: snapshot.version,
|
|
385
775
|
readPaths
|
|
386
776
|
});
|
|
387
|
-
yield new StepStoreWait2;
|
|
388
777
|
throw new Error("Store wait yielded control unexpectedly");
|
|
389
778
|
}
|
|
390
779
|
async function* takeStep(params) {
|
|
391
|
-
const { definition, id, event,
|
|
780
|
+
const { definition, id, event, stepKey, selector, claim } = params;
|
|
392
781
|
yield new StepKey(stepKey);
|
|
393
782
|
const cached = yield new StepCacheCheck;
|
|
394
|
-
if (cached)
|
|
395
|
-
yield cached;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
});
|
|
410
|
-
} catch (err) {
|
|
411
|
-
yield new StepError2(err);
|
|
412
|
-
throw err;
|
|
413
|
-
}
|
|
414
|
-
if (outcome.matched) {
|
|
415
|
-
yield new StepResult2(outcome.selected);
|
|
416
|
-
return outcome.selected;
|
|
417
|
-
}
|
|
418
|
-
await storeClient.registerWaiter({
|
|
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({
|
|
419
798
|
workflowId: event.workflowId,
|
|
420
799
|
executionId: event.executionId,
|
|
421
800
|
stepKey,
|
|
422
|
-
event,
|
|
423
801
|
storeName: definition.name,
|
|
424
802
|
storeId: id,
|
|
425
803
|
instanceId: outcome.instanceId,
|
|
426
804
|
sinceVersion: outcome.version,
|
|
427
805
|
readPaths: outcome.readPaths
|
|
428
806
|
});
|
|
429
|
-
yield new StepStoreWait2;
|
|
430
807
|
throw new Error("Store take yielded control unexpectedly");
|
|
431
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;
|
|
814
|
+
}
|
|
432
815
|
|
|
433
816
|
// src/exports/workflow.ts
|
|
434
817
|
import {
|
|
435
|
-
StepResponse as
|
|
818
|
+
StepResponse as StepResponse5,
|
|
436
819
|
StepKey as StepKey2,
|
|
437
820
|
StepError as StepError3,
|
|
438
821
|
StepResult as StepResult3,
|
|
439
822
|
StepDelay as StepDelay3,
|
|
440
823
|
StepCacheCheck as StepCacheCheck2,
|
|
441
|
-
|
|
824
|
+
StepEffectWait as StepEffectWait3,
|
|
825
|
+
StepInvalid as StepInvalid2,
|
|
442
826
|
StepStoreWait as StepStoreWait3,
|
|
443
|
-
WorkflowResult as WorkflowResult2
|
|
827
|
+
WorkflowResult as WorkflowResult2,
|
|
828
|
+
InfrastructureError as InfrastructureError4,
|
|
829
|
+
LeaseLostError,
|
|
830
|
+
StepConflictError,
|
|
831
|
+
isLeaseLost,
|
|
832
|
+
WORKFLOW_RESULT_STEP_KEY
|
|
444
833
|
} from "@yieldstar/core";
|
|
445
834
|
function workflow(workflowFn) {
|
|
446
|
-
return async function* workflowGenerator({
|
|
447
|
-
|
|
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 });
|
|
448
847
|
const workflowIterator = workflowFn(stepRunner, event, logger);
|
|
449
848
|
const { executionId } = event;
|
|
450
849
|
let iteratorResult = null;
|
|
451
850
|
let nextIteratorResult = null;
|
|
851
|
+
let resumeWith;
|
|
452
852
|
const stepKeys = new Set;
|
|
453
853
|
while (true) {
|
|
454
854
|
let stepAttempt = 0;
|
|
@@ -457,10 +857,12 @@ function workflow(workflowFn) {
|
|
|
457
857
|
iteratorResult = nextIteratorResult;
|
|
458
858
|
nextIteratorResult = null;
|
|
459
859
|
} else {
|
|
460
|
-
|
|
860
|
+
stopIfAborted(signal);
|
|
861
|
+
iteratorResult = await workflowIterator.next(resumeWith);
|
|
461
862
|
}
|
|
863
|
+
resumeWith = undefined;
|
|
462
864
|
if (iteratorResult.done) {
|
|
463
|
-
stepKey =
|
|
865
|
+
stepKey = WORKFLOW_RESULT_STEP_KEY;
|
|
464
866
|
} else if (!(iteratorResult.value instanceof StepKey2)) {
|
|
465
867
|
throw new Error("Step runners must yield a StepKey object");
|
|
466
868
|
} else {
|
|
@@ -470,10 +872,10 @@ function workflow(workflowFn) {
|
|
|
470
872
|
}
|
|
471
873
|
stepKeys.add(stepKey);
|
|
472
874
|
}
|
|
473
|
-
let cached = await heapClient.readStep({
|
|
875
|
+
let cached = await infrastructure(() => heapClient.readStep({
|
|
474
876
|
executionId,
|
|
475
877
|
stepKey
|
|
476
|
-
});
|
|
878
|
+
}));
|
|
477
879
|
if (cached?.meta) {
|
|
478
880
|
stepAttempt = cached.meta.attempt + 1;
|
|
479
881
|
}
|
|
@@ -490,64 +892,112 @@ function workflow(workflowFn) {
|
|
|
490
892
|
stepResponse = iteratorResult.value;
|
|
491
893
|
}
|
|
492
894
|
if (stepResponse instanceof StepCacheCheck2) {
|
|
493
|
-
const cachedResponse = cached ?
|
|
895
|
+
const cachedResponse = cached ? decodeStepRecord(cached.stepResponseJson) : null;
|
|
896
|
+
stopIfAborted(signal);
|
|
494
897
|
iteratorResult = await workflowIterator.next(cachedResponse);
|
|
495
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
|
+
}
|
|
496
914
|
} else if (!(stepResponse instanceof WorkflowResult2)) {
|
|
497
915
|
throw new Error("Step runners must yield a CacheCheck object");
|
|
498
916
|
}
|
|
499
917
|
if (isIterable(stepResponse)) {
|
|
500
918
|
console.log(`[ERR] Steps must be yielded using yield*
|
|
501
919
|
`);
|
|
502
|
-
stepResponse = new
|
|
503
|
-
} else if (!(stepResponse instanceof
|
|
920
|
+
stepResponse = new StepInvalid2;
|
|
921
|
+
} else if (!(stepResponse instanceof StepResponse5)) {
|
|
504
922
|
console.log(`[ERR] Iterators should yield a StepResponse. Got ${JSON.stringify(stepResponse)}`);
|
|
505
|
-
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;
|
|
506
930
|
}
|
|
507
931
|
const needsRetry = !cached?.meta.done && stepResponse instanceof StepError3 && stepResponse.maxAttempts > stepAttempt + 1;
|
|
508
|
-
const needsWake = !cached?.meta.done && stepResponse instanceof StepStoreWait3;
|
|
509
|
-
if (!cached) {
|
|
510
|
-
|
|
511
|
-
|
|
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,
|
|
512
938
|
stepKey,
|
|
513
939
|
stepAttempt,
|
|
514
|
-
|
|
515
|
-
stepResponseJson
|
|
516
|
-
});
|
|
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
|
+
}
|
|
517
949
|
}
|
|
518
950
|
if (stepResponse instanceof WorkflowResult2) {
|
|
519
951
|
return stepResponse;
|
|
520
952
|
}
|
|
521
|
-
if (stepResponse instanceof
|
|
953
|
+
if (iteratorResult.done && stepResponse instanceof StepError3) {
|
|
954
|
+
throw stepResponse.err;
|
|
955
|
+
}
|
|
956
|
+
if (stepResponse instanceof StepInvalid2) {
|
|
522
957
|
break;
|
|
523
958
|
}
|
|
524
959
|
if (stepResponse instanceof StepError3) {
|
|
525
960
|
if (needsRetry) {
|
|
526
|
-
yield new StepDelay3(
|
|
961
|
+
yield new StepDelay3(clock() + stepResponse.retryInterval);
|
|
527
962
|
} else {
|
|
963
|
+
stopIfAborted(signal);
|
|
528
964
|
nextIteratorResult = await workflowIterator.throw(stepResponse.err);
|
|
529
965
|
continue;
|
|
530
966
|
}
|
|
531
967
|
}
|
|
532
968
|
if (stepResponse instanceof StepDelay3) {
|
|
533
|
-
if (stepResponse.
|
|
969
|
+
if (stepResponse.wakeAt <= clock()) {
|
|
534
970
|
continue;
|
|
535
971
|
}
|
|
536
972
|
}
|
|
537
|
-
if (
|
|
973
|
+
if (stepResponse instanceof StepResult3) {
|
|
974
|
+
resumeWith = stepResponse;
|
|
975
|
+
} else {
|
|
538
976
|
yield stepResponse;
|
|
539
977
|
}
|
|
540
978
|
}
|
|
541
|
-
throw new Error("
|
|
979
|
+
throw new Error("Workflow yielded an invalid step. Steps must be yielded with yield* from a step runner.");
|
|
542
980
|
};
|
|
543
981
|
}
|
|
544
982
|
var createWorkflow = workflow;
|
|
983
|
+
function stopIfAborted(signal) {
|
|
984
|
+
if (signal.aborted)
|
|
985
|
+
throw new InfrastructureError4(signal.reason);
|
|
986
|
+
}
|
|
545
987
|
export {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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,
|
|
551
996
|
createHttpSdkFactory,
|
|
552
|
-
|
|
997
|
+
createLocalSdk,
|
|
998
|
+
createWorkflow,
|
|
999
|
+
createWorkflowRouter,
|
|
1000
|
+
defineStore,
|
|
1001
|
+
registerErrorClass2 as registerErrorClass,
|
|
1002
|
+
workflow
|
|
553
1003
|
};
|