@yieldstar/core 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.
Files changed (64) hide show
  1. package/dist/base/durable-value.d.ts +71 -0
  2. package/dist/base/durable-value.d.ts.map +1 -0
  3. package/dist/base/durable-value.test.d.ts +2 -0
  4. package/dist/base/durable-value.test.d.ts.map +1 -0
  5. package/dist/base/effect.d.ts +136 -0
  6. package/dist/base/effect.d.ts.map +1 -0
  7. package/dist/base/errors.d.ts +23 -0
  8. package/dist/base/errors.d.ts.map +1 -0
  9. package/dist/base/errors.test.d.ts +2 -0
  10. package/dist/base/errors.test.d.ts.map +1 -0
  11. package/dist/base/event.d.ts +6 -1
  12. package/dist/base/event.d.ts.map +1 -1
  13. package/dist/base/execution.d.ts +244 -0
  14. package/dist/base/execution.d.ts.map +1 -0
  15. package/dist/base/heap.d.ts +15 -3
  16. package/dist/base/heap.d.ts.map +1 -1
  17. package/dist/base/invoker.d.ts +47 -2
  18. package/dist/base/invoker.d.ts.map +1 -1
  19. package/dist/base/runtime.d.ts +27 -4
  20. package/dist/base/runtime.d.ts.map +1 -1
  21. package/dist/base/step.d.ts +21 -5
  22. package/dist/base/step.d.ts.map +1 -1
  23. package/dist/base/store.d.ts +135 -21
  24. package/dist/base/store.d.ts.map +1 -1
  25. package/dist/base/workflow.d.ts +15 -0
  26. package/dist/base/workflow.d.ts.map +1 -1
  27. package/dist/index.d.ts +23 -6
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1647 -172
  30. package/dist/lib/effect-state.d.ts +78 -0
  31. package/dist/lib/effect-state.d.ts.map +1 -0
  32. package/dist/lib/execution-dispatcher.d.ts +102 -0
  33. package/dist/lib/execution-dispatcher.d.ts.map +1 -0
  34. package/dist/lib/execution-state.d.ts +248 -0
  35. package/dist/lib/execution-state.d.ts.map +1 -0
  36. package/dist/lib/execution-state.test.d.ts +2 -0
  37. package/dist/lib/execution-state.test.d.ts.map +1 -0
  38. package/dist/lib/wait-for-outcome.d.ts +15 -0
  39. package/dist/lib/wait-for-outcome.d.ts.map +1 -0
  40. package/dist/lib/workflow-runner.d.ts +14 -9
  41. package/dist/lib/workflow-runner.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/base/durable-value.test.ts +285 -0
  44. package/src/base/durable-value.ts +442 -0
  45. package/src/base/effect.ts +165 -0
  46. package/src/base/errors.test.ts +210 -0
  47. package/src/base/errors.ts +72 -0
  48. package/src/base/event.ts +6 -1
  49. package/src/base/execution.ts +303 -0
  50. package/src/base/heap.ts +17 -3
  51. package/src/base/invoker.ts +60 -2
  52. package/src/base/runtime.ts +28 -7
  53. package/src/base/step.ts +29 -7
  54. package/src/base/store.ts +339 -49
  55. package/src/base/workflow.ts +15 -0
  56. package/src/index.ts +137 -3
  57. package/src/lib/effect-state.ts +254 -0
  58. package/src/lib/execution-dispatcher.ts +393 -0
  59. package/src/lib/execution-state.test.ts +249 -0
  60. package/src/lib/execution-state.ts +650 -0
  61. package/src/lib/wait-for-outcome.ts +40 -0
  62. package/src/lib/workflow-runner.ts +75 -43
  63. package/src/base/scheduler.ts +0 -8
  64. package/tsconfig.tsbuildinfo +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,1356 @@
1
+ // src/base/invoker.ts
2
+ var WORKER_EXITED = Symbol.for("yieldstar.workerExited");
3
+
4
+ class WorkerExitedError extends Error {
5
+ [WORKER_EXITED] = true;
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "WorkerExitedError";
9
+ }
10
+ static is(value) {
11
+ return typeof value === "object" && value !== null && value[WORKER_EXITED] === true;
12
+ }
13
+ }
14
+ // src/base/runtime.ts
15
+ class Runtime {
16
+ }
17
+ // src/base/execution.ts
18
+ function isLeaseLost(value) {
19
+ return typeof value === "object" && value !== null && value.status === "lost";
20
+ }
21
+
22
+ class LeaseLostError extends Error {
23
+ executionId;
24
+ constructor(executionId) {
25
+ super(`Lease on execution "${executionId}" was lost`);
26
+ this.name = "LeaseLostError";
27
+ this.executionId = executionId;
28
+ }
29
+ }
30
+
31
+ class StepConflictError extends Error {
32
+ executionId;
33
+ stepKey;
34
+ constructor(executionId, stepKey) {
35
+ super(`Step "${stepKey}" of execution "${executionId}" is already recorded with a different value`);
36
+ this.name = "StepConflictError";
37
+ this.executionId = executionId;
38
+ this.stepKey = stepKey;
39
+ }
40
+ }
41
+
42
+ class ExecutionAdmissionError extends Error {
43
+ code;
44
+ constructor(code, message) {
45
+ super(message);
46
+ this.name = "ExecutionAdmissionError";
47
+ this.code = code;
48
+ }
49
+ }
50
+
51
+ class WorkflowStartError extends Error {
52
+ code;
53
+ constructor(code, message) {
54
+ super(message);
55
+ this.name = "WorkflowStartError";
56
+ this.code = code;
57
+ }
58
+ }
59
+ var INFRASTRUCTURE_ERROR = Symbol.for("yieldstar.infrastructureError");
60
+
61
+ class InfrastructureError extends Error {
62
+ [INFRASTRUCTURE_ERROR] = true;
63
+ constructor(cause) {
64
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
65
+ this.name = "InfrastructureError";
66
+ }
67
+ static is(value) {
68
+ return typeof value === "object" && value !== null && value[INFRASTRUCTURE_ERROR] === true;
69
+ }
70
+ }
71
+ function isTerminalExecutionStatus(status) {
72
+ return status === "completed" || status === "failed";
73
+ }
74
+ // src/base/effect.ts
75
+ class EffectClientInvariantError extends Error {
76
+ code = "EFFECT_RUNTIME_INVARIANT";
77
+ constructor(message, options) {
78
+ super(message, options);
79
+ this.name = "EffectClientInvariantError";
80
+ }
81
+ }
82
+
83
+ class EffectDeliveryError extends Error {
84
+ code;
85
+ effectId;
86
+ stepKey;
87
+ constructor(code, params, options) {
88
+ super(params.message, options);
89
+ this.name = "EffectDeliveryError";
90
+ this.code = code;
91
+ this.effectId = params.effectId;
92
+ this.stepKey = params.stepKey;
93
+ }
94
+ }
95
+
96
+ class WorkflowEffectError extends Error {
97
+ code;
98
+ constructor(code, message) {
99
+ super(message);
100
+ this.name = "WorkflowEffectError";
101
+ this.code = code;
102
+ }
103
+ }
104
+ // src/base/errors.ts
105
+ var WORKFLOW_RESULT_STEP_KEY = "$$workflow-result$$";
106
+
107
+ class WorkflowValueError extends Error {
108
+ code;
109
+ stepKey;
110
+ constructor(code, stepKey, message) {
111
+ super(message);
112
+ this.name = "WorkflowValueError";
113
+ this.code = code;
114
+ this.stepKey = stepKey;
115
+ }
116
+ }
117
+ var errorClasses = new Map;
118
+ function registerErrorClass(name, errorClass) {
119
+ if (typeof name !== "string" || name === "") {
120
+ throw new TypeError("Error class name must be a non-empty string");
121
+ }
122
+ if (typeof errorClass !== "function" || !(errorClass.prototype instanceof Error || errorClass === Error)) {
123
+ throw new TypeError(`Error class "${name}" must extend Error`);
124
+ }
125
+ errorClasses.set(name, errorClass);
126
+ }
127
+ function registeredErrorClass(name) {
128
+ return errorClasses.get(name);
129
+ }
130
+ var defaults = [
131
+ ["TypeError", TypeError],
132
+ ["RangeError", RangeError],
133
+ ["SyntaxError", SyntaxError],
134
+ ["ReferenceError", ReferenceError],
135
+ ["EvalError", EvalError],
136
+ ["URIError", URIError],
137
+ ["AggregateError", AggregateError],
138
+ ["WorkflowStartError", WorkflowStartError],
139
+ ["WorkflowValueError", WorkflowValueError],
140
+ ["WorkflowEffectError", WorkflowEffectError],
141
+ ["EffectClientInvariantError", EffectClientInvariantError],
142
+ ["EffectDeliveryError", EffectDeliveryError]
143
+ ];
144
+ for (const [name, errorClass] of defaults)
145
+ registerErrorClass(name, errorClass);
146
+
147
+ // src/base/durable-value.ts
148
+ class DurableValueError extends Error {
149
+ }
150
+ var durableValueCodec = {
151
+ encode(value) {
152
+ try {
153
+ return { status: "encoded", canonical: encodeValue(value, "$", new Set) };
154
+ } catch (err) {
155
+ if (!(err instanceof DurableValueError))
156
+ throw err;
157
+ return { status: "invalid", message: err.message };
158
+ }
159
+ },
160
+ decode(canonical) {
161
+ let parsed;
162
+ try {
163
+ parsed = JSON.parse(canonical);
164
+ } catch (err) {
165
+ return { status: "invalid", message: `not JSON: ${err.message}` };
166
+ }
167
+ try {
168
+ return { status: "decoded", value: decodeParsedValue(parsed) };
169
+ } catch (err) {
170
+ if (!(err instanceof DurableValueError))
171
+ throw err;
172
+ return { status: "invalid", message: err.message };
173
+ }
174
+ }
175
+ };
176
+ function encodeThrownValue(value) {
177
+ const encoding = durableValueCodec.encode(value);
178
+ if (encoding.status === "encoded")
179
+ return encoding.canonical;
180
+ let message;
181
+ try {
182
+ message = String(value);
183
+ } catch {
184
+ message = "Thrown value is not a durable value";
185
+ }
186
+ return encodeValue(new Error(message), "$", new Set);
187
+ }
188
+ function isPlainObject(value) {
189
+ if (typeof value !== "object" || value === null)
190
+ return false;
191
+ const proto = Object.getPrototypeOf(value);
192
+ return proto === Object.prototype || proto === null;
193
+ }
194
+ var UNDEFINED_TAG = '{"$type":"undefined"}';
195
+ var ERROR_KEYS = new Set(["name", "message", "stack", "code", "cause"]);
196
+ function encodeValue(value, path, ancestors) {
197
+ if (value === null)
198
+ return "null";
199
+ switch (typeof value) {
200
+ case "undefined":
201
+ return UNDEFINED_TAG;
202
+ case "boolean":
203
+ return value ? "true" : "false";
204
+ case "string":
205
+ return JSON.stringify(value);
206
+ case "number":
207
+ if (!Number.isFinite(value)) {
208
+ throw new DurableValueError(`${path}: ${value} is not a finite number`);
209
+ }
210
+ return JSON.stringify(value);
211
+ case "object":
212
+ break;
213
+ default:
214
+ throw new DurableValueError(`${path}: ${typeof value} is not a durable value`);
215
+ }
216
+ if (ancestors.has(value)) {
217
+ throw new DurableValueError(`${path}: reference cycle`);
218
+ }
219
+ ancestors.add(value);
220
+ try {
221
+ if (Array.isArray(value))
222
+ return encodeArray(value, path, ancestors);
223
+ if (value instanceof Error)
224
+ return encodeError(value, path, ancestors);
225
+ return encodeObject(value, path, ancestors);
226
+ } finally {
227
+ ancestors.delete(value);
228
+ }
229
+ }
230
+ function encodeArray(value, path, ancestors) {
231
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
232
+ throw new DurableValueError(`${path}: arrays must not be subclassed`);
233
+ }
234
+ const items = [];
235
+ for (const key of Reflect.ownKeys(value)) {
236
+ if (key === "length")
237
+ continue;
238
+ if (typeof key === "symbol") {
239
+ throw new DurableValueError(`${path}: symbol properties are not durable`);
240
+ }
241
+ if (!isArrayIndex(key, value.length)) {
242
+ throw new DurableValueError(`${path}.${key}: arrays must only have items`);
243
+ }
244
+ }
245
+ for (let i = 0;i < value.length; i++) {
246
+ const itemPath = `${path}[${i}]`;
247
+ const descriptor = Object.getOwnPropertyDescriptor(value, i);
248
+ if (!descriptor) {
249
+ throw new DurableValueError(`${itemPath}: missing array item`);
250
+ }
251
+ items.push(encodeValue(dataValue(descriptor, itemPath), itemPath, ancestors));
252
+ }
253
+ return `[${items.join(",")}]`;
254
+ }
255
+ function encodeObject(value, path, ancestors) {
256
+ if (!isPlainObject(value)) {
257
+ const name = Object.getPrototypeOf(value)?.constructor?.name ?? "object";
258
+ throw new DurableValueError(`${path}: ${name} instances are not plain objects`);
259
+ }
260
+ const entries = [];
261
+ for (const key of Reflect.ownKeys(value)) {
262
+ if (typeof key === "symbol") {
263
+ throw new DurableValueError(`${path}: symbol properties are not durable`);
264
+ }
265
+ const keyPath = `${path}[${JSON.stringify(key)}]`;
266
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
267
+ entries.push([key, encodeValue(dataValue(descriptor, keyPath), keyPath, ancestors)]);
268
+ }
269
+ return objectLiteral(entries);
270
+ }
271
+ function objectLiteral(entries) {
272
+ const literal = sortedLiteral(entries);
273
+ return entries.some(([key]) => key === "$type") ? `{"$type":"object","value":${literal}}` : literal;
274
+ }
275
+ function sortedLiteral(entries) {
276
+ const sorted = [...entries].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
277
+ return `{${sorted.map(([key, value]) => `${JSON.stringify(key)}:${value}`).join(",")}}`;
278
+ }
279
+ function encodeError(error, path, ancestors) {
280
+ const name = readProperty(error, "name");
281
+ const message = readProperty(error, "message");
282
+ const stack = readProperty(error, "stack");
283
+ const code = readProperty(error, "code");
284
+ const tag = [
285
+ ["$type", '"error"'],
286
+ ["name", JSON.stringify(typeof name === "string" ? name : "Error")],
287
+ ["message", JSON.stringify(typeof message === "string" ? message : "")]
288
+ ];
289
+ if (typeof stack === "string")
290
+ tag.push(["stack", JSON.stringify(stack)]);
291
+ if (typeof code === "string")
292
+ tag.push(["code", JSON.stringify(code)]);
293
+ if (Object.hasOwn(error, "cause")) {
294
+ const cause = encodeOptional(readProperty(error, "cause"), `${path}.cause`, ancestors);
295
+ if (cause !== undefined)
296
+ tag.push(["cause", cause]);
297
+ }
298
+ const errors = Object.getOwnPropertyDescriptor(error, "errors");
299
+ if (errors && !errors.enumerable && Array.isArray(errors.value)) {
300
+ const encoded = encodeOptional(errors.value, `${path}.errors`, ancestors);
301
+ if (encoded !== undefined)
302
+ tag.push(["errors", encoded]);
303
+ }
304
+ const fields = [];
305
+ for (const key of Object.keys(error)) {
306
+ if (ERROR_KEYS.has(key))
307
+ continue;
308
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
309
+ if (!("value" in descriptor))
310
+ continue;
311
+ const encoded = encodeOptional(descriptor.value, `${path}.${key}`, ancestors);
312
+ if (encoded !== undefined)
313
+ fields.push([key, encoded]);
314
+ }
315
+ if (fields.length > 0)
316
+ tag.push(["fields", objectLiteral(fields)]);
317
+ return sortedLiteral(tag);
318
+ }
319
+ function encodeOptional(value, path, ancestors) {
320
+ try {
321
+ return encodeValue(value, path, ancestors);
322
+ } catch (err) {
323
+ if (!(err instanceof DurableValueError))
324
+ throw err;
325
+ return;
326
+ }
327
+ }
328
+ function readProperty(error, key) {
329
+ try {
330
+ return error[key];
331
+ } catch {
332
+ return;
333
+ }
334
+ }
335
+ function dataValue(descriptor, path) {
336
+ if (!("value" in descriptor)) {
337
+ throw new DurableValueError(`${path}: accessor properties are not durable`);
338
+ }
339
+ if (!descriptor.enumerable) {
340
+ throw new DurableValueError(`${path}: non-enumerable properties are not durable`);
341
+ }
342
+ return descriptor.value;
343
+ }
344
+ function isArrayIndex(key, length) {
345
+ const index = Number(key);
346
+ return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
347
+ }
348
+ function decodeParsedValue(parsed, path = "$") {
349
+ if (parsed === null)
350
+ return null;
351
+ switch (typeof parsed) {
352
+ case "boolean":
353
+ case "string":
354
+ return parsed;
355
+ case "number":
356
+ if (!Number.isFinite(parsed)) {
357
+ throw new DurableValueError(`${path}: ${parsed} is not a finite number`);
358
+ }
359
+ return parsed;
360
+ case "object":
361
+ break;
362
+ default:
363
+ throw new DurableValueError(`${path}: ${typeof parsed} is not a durable value`);
364
+ }
365
+ if (Array.isArray(parsed)) {
366
+ return parsed.map((item, i) => decodeParsedValue(item, `${path}[${i}]`));
367
+ }
368
+ const object = parsed;
369
+ if (!Object.hasOwn(object, "$type"))
370
+ return decodeObject(object, path);
371
+ switch (object.$type) {
372
+ case "undefined":
373
+ expectKeys(object, path, ["$type"], []);
374
+ return;
375
+ case "object": {
376
+ expectKeys(object, path, ["$type", "value"], []);
377
+ const value = object.value;
378
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
379
+ throw new DurableValueError(`${path}.value: a wrapped object must be an object`);
380
+ }
381
+ return decodeObject(value, `${path}.value`);
382
+ }
383
+ case "error":
384
+ return decodeError(object, path);
385
+ default:
386
+ throw new DurableValueError(`${path}: unknown $type ${JSON.stringify(object.$type)}`);
387
+ }
388
+ }
389
+ function decodeObject(object, path) {
390
+ const decoded = {};
391
+ for (const key of Object.keys(object)) {
392
+ Object.defineProperty(decoded, key, {
393
+ value: decodeParsedValue(object[key], `${path}[${JSON.stringify(key)}]`),
394
+ enumerable: true,
395
+ writable: true,
396
+ configurable: true
397
+ });
398
+ }
399
+ return decoded;
400
+ }
401
+ function decodeError(tag, path) {
402
+ expectKeys(tag, path, ["$type", "name", "message"], ["stack", "code", "cause", "errors", "fields"]);
403
+ const { name, message, stack, code } = tag;
404
+ for (const [key, value, optional] of [
405
+ ["name", name, false],
406
+ ["message", message, false],
407
+ ["stack", stack, true],
408
+ ["code", code, true]
409
+ ]) {
410
+ if (typeof value !== "string" && !(optional && value === undefined)) {
411
+ throw new DurableValueError(`${path}.${key}: must be a string`);
412
+ }
413
+ }
414
+ const errorClass = registeredErrorClass(name) ?? Error;
415
+ const error = Reflect.construct(Error, [], errorClass);
416
+ const hidden = (key, value) => Object.defineProperty(error, key, {
417
+ value,
418
+ enumerable: false,
419
+ writable: true,
420
+ configurable: true
421
+ });
422
+ const visible = (key, value) => Object.defineProperty(error, key, {
423
+ value,
424
+ enumerable: true,
425
+ writable: true,
426
+ configurable: true
427
+ });
428
+ if (error.name !== name)
429
+ visible("name", name);
430
+ hidden("message", message);
431
+ if (stack !== undefined)
432
+ hidden("stack", stack);
433
+ else
434
+ delete error.stack;
435
+ if (code !== undefined)
436
+ visible("code", code);
437
+ if (Object.hasOwn(tag, "cause")) {
438
+ hidden("cause", decodeParsedValue(tag.cause, `${path}.cause`));
439
+ }
440
+ if (Object.hasOwn(tag, "errors")) {
441
+ if (!Array.isArray(tag.errors)) {
442
+ throw new DurableValueError(`${path}.errors: must be an array`);
443
+ }
444
+ hidden("errors", decodeParsedValue(tag.errors, `${path}.errors`));
445
+ }
446
+ if (Object.hasOwn(tag, "fields")) {
447
+ const fields = decodeParsedValue(tag.fields, `${path}.fields`);
448
+ if (!isPlainObject(fields)) {
449
+ throw new DurableValueError(`${path}.fields: must be an object`);
450
+ }
451
+ for (const key of Object.keys(fields))
452
+ visible(key, fields[key]);
453
+ }
454
+ return error;
455
+ }
456
+ function expectKeys(object, path, required, optional) {
457
+ for (const key of required) {
458
+ if (!Object.hasOwn(object, key)) {
459
+ throw new DurableValueError(`${path}: tag is missing ${key}`);
460
+ }
461
+ }
462
+ for (const key of Object.keys(object)) {
463
+ if (!required.includes(key) && !optional.includes(key)) {
464
+ throw new DurableValueError(`${path}: unexpected tag key ${JSON.stringify(key)}`);
465
+ }
466
+ }
467
+ }
468
+
469
+ // src/lib/effect-state.ts
470
+ function validateEffectRequest(request) {
471
+ if (!isNonEmptyString(request.effectId)) {
472
+ return { ok: false, message: "effectId must be a non-empty string" };
473
+ }
474
+ if (!isNonEmptyString(request.operation)) {
475
+ return { ok: false, message: "operation must be a non-empty string" };
476
+ }
477
+ if (!isPlainObject(request.input)) {
478
+ return { ok: false, message: "input must be a plain object" };
479
+ }
480
+ const encoding = durableValueCodec.encode(request.input);
481
+ if (encoding.status === "invalid") {
482
+ return { ok: false, message: `input: ${encoding.message}` };
483
+ }
484
+ return { ok: true, input: encoding.canonical };
485
+ }
486
+ function prepareEffectBegin(request) {
487
+ const validated = validateEffectRequest(request);
488
+ if (!validated.ok)
489
+ throw invariant(`Invalid effect request: ${validated.message}`);
490
+ assertStepId(request);
491
+ return {
492
+ effectId: request.effectId,
493
+ operation: request.operation,
494
+ input: validated.input,
495
+ executionId: request.executionId,
496
+ stepKey: request.stepKey
497
+ };
498
+ }
499
+ function prepareEffectSettle(request) {
500
+ if (!isNonEmptyString(request.effectId)) {
501
+ throw invariant("Invalid effect settlement: effectId must be a non-empty string");
502
+ }
503
+ if (!isNonEmptyString(request.operation)) {
504
+ throw invariant("Invalid effect settlement: operation must be a non-empty string");
505
+ }
506
+ const encoding = durableValueCodec.encode(request.result);
507
+ if (encoding.status === "invalid") {
508
+ throw invariant(`Invalid effect settlement: result: ${encoding.message}`);
509
+ }
510
+ return {
511
+ effectId: request.effectId,
512
+ operation: request.operation,
513
+ result: encoding.canonical
514
+ };
515
+ }
516
+ function prepareEffectWatch(request) {
517
+ if (!isNonEmptyString(request.effectId)) {
518
+ throw invariant("Invalid effect watch: effectId must be a non-empty string");
519
+ }
520
+ assertStepId(request);
521
+ return {
522
+ effectId: request.effectId,
523
+ executionId: request.executionId,
524
+ stepKey: request.stepKey
525
+ };
526
+ }
527
+ function createEffectState(begin) {
528
+ return {
529
+ effectId: begin.effectId,
530
+ operation: begin.operation,
531
+ input: begin.input,
532
+ status: "pending",
533
+ conflicts: []
534
+ };
535
+ }
536
+ function matchEffectBegin(state, begin) {
537
+ if (state.operation !== begin.operation || state.input !== begin.input) {
538
+ return { status: "rejected", code: "EFFECT_REQUEST_MISMATCH" };
539
+ }
540
+ if (state.status === "pending")
541
+ return { status: "pending" };
542
+ return { status: "settled", result: decodeSettledResult(state) };
543
+ }
544
+ function settleEffect(state, settle, now) {
545
+ if (state.operation !== settle.operation) {
546
+ throw invariant(`Cannot settle effect "${state.effectId}" as operation "${settle.operation}": it was begun as "${state.operation}"`);
547
+ }
548
+ if (state.status === "pending") {
549
+ return {
550
+ state: { ...state, status: "settled", result: settle.result },
551
+ result: { status: "settled", result: decodeValue(settle.result, state.effectId) },
552
+ wake: true
553
+ };
554
+ }
555
+ const result = decodeSettledResult(state);
556
+ if (state.result === settle.result) {
557
+ return { state, result: { status: "settled", result }, wake: false };
558
+ }
559
+ const conflictingResult = decodeValue(settle.result, state.effectId);
560
+ const reported = state.conflicts.some((conflict) => conflict.conflictingResult === settle.result);
561
+ return {
562
+ state: reported ? state : {
563
+ ...state,
564
+ conflicts: [
565
+ ...state.conflicts,
566
+ { result: state.result, conflictingResult: settle.result, reportedAt: now }
567
+ ]
568
+ },
569
+ result: { status: "conflict", result, conflictingResult },
570
+ wake: false
571
+ };
572
+ }
573
+ function toEffectRecord(state) {
574
+ const record = {
575
+ effectId: state.effectId,
576
+ operation: state.operation,
577
+ input: decodeInput(state),
578
+ status: state.status,
579
+ conflicts: state.conflicts.map((conflict) => ({
580
+ result: decodeValue(conflict.result, state.effectId),
581
+ conflictingResult: decodeValue(conflict.conflictingResult, state.effectId),
582
+ reportedAt: conflict.reportedAt
583
+ }))
584
+ };
585
+ if (state.status === "settled")
586
+ record.result = decodeSettledResult(state);
587
+ return record;
588
+ }
589
+ function effectNotFound(effectId) {
590
+ return invariant(`Effect "${effectId}" does not exist: begin it first`);
591
+ }
592
+ function decodeSettledResult(state) {
593
+ if (state.result === undefined) {
594
+ throw invariant(`Settled effect "${state.effectId}" has no stored result`);
595
+ }
596
+ return decodeValue(state.result, state.effectId);
597
+ }
598
+ function decodeInput(state) {
599
+ const input = decodeValue(state.input, state.effectId);
600
+ if (!isPlainObject(input)) {
601
+ throw invariant(`Stored input of effect "${state.effectId}" is not an object`);
602
+ }
603
+ return input;
604
+ }
605
+ function decodeValue(canonical, effectId) {
606
+ const decoded = durableValueCodec.decode(canonical);
607
+ if (decoded.status === "invalid") {
608
+ throw invariant(`Stored value of effect "${effectId}" is invalid: ${decoded.message}`);
609
+ }
610
+ return decoded.value;
611
+ }
612
+ function assertStepId(request) {
613
+ if (!isNonEmptyString(request.executionId)) {
614
+ throw invariant("Invalid effect request: executionId must be a non-empty string");
615
+ }
616
+ if (!isNonEmptyString(request.stepKey)) {
617
+ throw invariant("Invalid effect request: stepKey must be a non-empty string");
618
+ }
619
+ }
620
+ function isNonEmptyString(value) {
621
+ return typeof value === "string" && value !== "";
622
+ }
623
+ function invariant(message) {
624
+ return new EffectClientInvariantError(message);
625
+ }
626
+ // src/utils/error.ts
627
+ function errorWithOriginalStack(error, fn) {
628
+ const err = new Error(error.message);
629
+ Error.captureStackTrace(err, fn);
630
+ err.stack = error.stack;
631
+ throw err;
632
+ }
633
+
634
+ // src/utils/map.ts
635
+ class ReadOnlyMap {
636
+ sourceMap;
637
+ constructor(sourceMap) {
638
+ if (sourceMap instanceof Map) {
639
+ this.sourceMap = sourceMap;
640
+ } else if (typeof sourceMap === "object" && sourceMap !== null) {
641
+ this.sourceMap = new Map(Object.entries(sourceMap));
642
+ } else {
643
+ this.sourceMap = new Map;
644
+ }
645
+ }
646
+ get(key) {
647
+ return this.sourceMap.get(key);
648
+ }
649
+ has(key) {
650
+ return this.sourceMap.has(key);
651
+ }
652
+ get size() {
653
+ return this.sourceMap.size;
654
+ }
655
+ entries() {
656
+ return this.sourceMap.entries();
657
+ }
658
+ keys() {
659
+ return this.sourceMap.keys();
660
+ }
661
+ values() {
662
+ return this.sourceMap.values();
663
+ }
664
+ forEach(callbackfn, thisArg) {
665
+ return this.sourceMap.forEach(callbackfn, thisArg);
666
+ }
667
+ [Symbol.iterator]() {
668
+ return this.sourceMap[Symbol.iterator]();
669
+ }
670
+ }
671
+
672
+ class FreezableMap extends Map {
673
+ frozen = false;
674
+ freeze() {
675
+ this.frozen = true;
676
+ }
677
+ isFrozen() {
678
+ return this.frozen;
679
+ }
680
+ set(key, value) {
681
+ if (this.frozen) {
682
+ const error = new Error("Cannot call set on a frozen map");
683
+ throw errorWithOriginalStack(error, this.set);
684
+ }
685
+ return super.set(key, value);
686
+ }
687
+ delete(key) {
688
+ if (this.frozen) {
689
+ const error = new Error("Cannot call delete on a frozen map");
690
+ throw errorWithOriginalStack(error, this.delete);
691
+ }
692
+ return super.delete(key);
693
+ }
694
+ clear() {
695
+ if (this.frozen) {
696
+ const error = new Error("Cannot call clear on a frozen map");
697
+ throw errorWithOriginalStack(error, this.clear);
698
+ }
699
+ return super.clear();
700
+ }
701
+ }
702
+
703
+ // src/lib/execution-state.ts
704
+ var DEFAULT_LEASE_DURATION = 30000;
705
+ var REDELIVERY_DELAY_BASE = 1000;
706
+ var REDELIVERY_DELAY_MAX = 300000;
707
+ function redeliveryDelay(unacknowledgedDeliveryCount) {
708
+ if (unacknowledgedDeliveryCount <= 0)
709
+ return 0;
710
+ const exponent = Math.min(unacknowledgedDeliveryCount - 1, 30);
711
+ return Math.min(REDELIVERY_DELAY_BASE * 2 ** exponent, REDELIVERY_DELAY_MAX);
712
+ }
713
+
714
+ class ExecutionEventEncodingError extends Error {
715
+ constructor(message) {
716
+ super(message);
717
+ this.name = "ExecutionEventEncodingError";
718
+ }
719
+ }
720
+ function encodeExecutionEvent(event) {
721
+ const params = durableValueCodec.encode(event.params);
722
+ if (params.status === "invalid") {
723
+ throw new ExecutionEventEncodingError(`params${params.message.slice(1)}`);
724
+ }
725
+ const entries = [];
726
+ for (const [key, value] of event.context?.entries() ?? []) {
727
+ if (typeof key !== "string") {
728
+ throw new ExecutionEventEncodingError(`context: keys must be strings, got ${typeof key}`);
729
+ }
730
+ entries.push([key, value]);
731
+ }
732
+ const context = durableValueCodec.encode(entries);
733
+ if (context.status === "invalid") {
734
+ throw new ExecutionEventEncodingError(`context${context.message.slice(1)}`);
735
+ }
736
+ return { params: params.canonical, context: context.canonical };
737
+ }
738
+ function decodeExecutionEvent(encoded) {
739
+ const entries = decodeOrThrow(encoded.context, "context");
740
+ if (!Array.isArray(entries) || !entries.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string")) {
741
+ throw new Error("Invalid execution context encoding: expected [key, value] entries");
742
+ }
743
+ return {
744
+ params: decodeOrThrow(encoded.params, "params"),
745
+ context: new ReadOnlyMap(new Map(entries))
746
+ };
747
+ }
748
+ function encodeExecutionOutcome(outcome) {
749
+ if (outcome.status === "completed") {
750
+ const result = durableValueCodec.encode(outcome.result);
751
+ if (result.status === "encoded") {
752
+ return `{"result":${result.canonical},"status":"completed"}`;
753
+ }
754
+ return encodeFailedOutcome(new WorkflowValueError("INVALID_STEP_VALUE", WORKFLOW_RESULT_STEP_KEY, `Workflow result is not a durable value: ${result.message}`));
755
+ }
756
+ return encodeFailedOutcome(outcome.error);
757
+ }
758
+ function encodeFailedOutcome(error) {
759
+ const encoded = encodeThrownValue(error instanceof Error ? error : new Error(safeString(error)));
760
+ return `{"error":${encoded},"status":"failed"}`;
761
+ }
762
+ function decodeExecutionOutcome(encoded) {
763
+ const parsed = JSON.parse(encoded);
764
+ if (parsed.status === "completed") {
765
+ return { status: "completed", result: decodeParsedValue(parsed.result, "result") };
766
+ }
767
+ const error = decodeParsedValue(parsed.error, "error");
768
+ if (!(error instanceof Error)) {
769
+ throw new Error("Invalid failed outcome encoding: expected an error");
770
+ }
771
+ return { status: "failed", error };
772
+ }
773
+ function decodeOrThrow(canonical, label) {
774
+ const decoding = durableValueCodec.decode(canonical);
775
+ if (decoding.status === "invalid") {
776
+ throw new Error(`Invalid ${label} encoding: ${decoding.message}`);
777
+ }
778
+ return decoding.value;
779
+ }
780
+ function safeString(value) {
781
+ try {
782
+ return String(value);
783
+ } catch {
784
+ return "Thrown value is not a durable value";
785
+ }
786
+ }
787
+ function createExecutionState(params) {
788
+ return {
789
+ executionId: params.executionId,
790
+ workflowId: params.workflowId,
791
+ params: params.encoded.params,
792
+ context: params.encoded.context,
793
+ status: "admitted",
794
+ outcome: null,
795
+ admittedAt: params.now,
796
+ updatedAt: params.now,
797
+ dueAt: params.now,
798
+ claimedDueAt: null,
799
+ leaseToken: 0,
800
+ leaseExpiresAt: null,
801
+ unacknowledgedDeliveryCount: 0,
802
+ redeliverAt: null,
803
+ parent: params.parent ?? null
804
+ };
805
+ }
806
+ function hasExpiredLease(state, now) {
807
+ return state.leaseExpiresAt !== null && state.leaseExpiresAt <= now;
808
+ }
809
+ function executionStatusAt(state, now) {
810
+ if (state.leaseExpiresAt !== null && state.leaseExpiresAt > now) {
811
+ return "running";
812
+ }
813
+ return state.status;
814
+ }
815
+ function unacknowledgedDeliveryCountAt(state, now) {
816
+ return state.unacknowledgedDeliveryCount + (hasExpiredLease(state, now) ? 1 : 0);
817
+ }
818
+ function toExecutionRecord(state, now) {
819
+ const event = decodeExecutionEvent(state);
820
+ return {
821
+ executionId: state.executionId,
822
+ workflowId: state.workflowId,
823
+ params: event.params,
824
+ context: event.context,
825
+ status: executionStatusAt(state, now),
826
+ ...state.outcome !== null ? { outcome: decodeExecutionOutcome(state.outcome) } : {},
827
+ admittedAt: state.admittedAt,
828
+ updatedAt: state.updatedAt,
829
+ unacknowledgedDeliveryCount: unacknowledgedDeliveryCountAt(state, now),
830
+ ...state.parent !== null ? { parent: { ...state.parent } } : {}
831
+ };
832
+ }
833
+ function nextClaimAt(state) {
834
+ if (isTerminalExecutionStatus(state.status))
835
+ return null;
836
+ if (state.leaseExpiresAt !== null) {
837
+ const due = earliest(state.dueAt, state.claimedDueAt);
838
+ if (due === null)
839
+ return null;
840
+ return Math.max(due, state.leaseExpiresAt + redeliveryDelay(state.unacknowledgedDeliveryCount + 1));
841
+ }
842
+ if (state.dueAt === null)
843
+ return null;
844
+ return state.redeliverAt === null ? state.dueAt : Math.max(state.dueAt, state.redeliverAt);
845
+ }
846
+ function isExecutionClaimable(state, now) {
847
+ const at = nextClaimAt(state);
848
+ return at !== null && at <= now;
849
+ }
850
+ function isLeaseCurrent(state, lease) {
851
+ return state.executionId === lease.executionId && state.leaseExpiresAt !== null && state.leaseToken === lease.token;
852
+ }
853
+ var LOST = { status: "lost" };
854
+ function requestExecutionWake(state, wakeAt, now) {
855
+ if (isTerminalExecutionStatus(state.status))
856
+ return null;
857
+ return { ...state, dueAt: earliest(state.dueAt, wakeAt), updatedAt: now };
858
+ }
859
+ function claimExecution(state, params) {
860
+ const lease = {
861
+ executionId: state.executionId,
862
+ token: state.leaseToken + 1,
863
+ expiresAt: params.now + params.duration
864
+ };
865
+ return {
866
+ state: {
867
+ ...state,
868
+ unacknowledgedDeliveryCount: state.unacknowledgedDeliveryCount + (state.leaseExpiresAt !== null ? 1 : 0),
869
+ claimedDueAt: earliest(state.dueAt, state.claimedDueAt),
870
+ dueAt: null,
871
+ leaseToken: lease.token,
872
+ leaseExpiresAt: lease.expiresAt,
873
+ redeliverAt: null
874
+ },
875
+ lease
876
+ };
877
+ }
878
+ function renewExecutionLease(state, params) {
879
+ if (!isLeaseCurrent(state, params.lease))
880
+ return { state, result: LOST };
881
+ const lease = {
882
+ ...params.lease,
883
+ expiresAt: params.now + params.duration
884
+ };
885
+ return {
886
+ state: { ...state, leaseExpiresAt: lease.expiresAt },
887
+ result: { status: "renewed", lease }
888
+ };
889
+ }
890
+ function releaseExecutionLease(state, params) {
891
+ if (!isLeaseCurrent(state, params.lease))
892
+ return { state, result: LOST };
893
+ const unacknowledgedDeliveryCount = state.unacknowledgedDeliveryCount + 1;
894
+ return {
895
+ state: {
896
+ ...state,
897
+ dueAt: earliest(state.dueAt, state.claimedDueAt),
898
+ claimedDueAt: null,
899
+ leaseExpiresAt: null,
900
+ unacknowledgedDeliveryCount,
901
+ redeliverAt: params.now + redeliveryDelay(unacknowledgedDeliveryCount)
902
+ },
903
+ result: { status: "released" }
904
+ };
905
+ }
906
+ function acknowledgeExecution(state, params) {
907
+ if (!isLeaseCurrent(state, params.lease))
908
+ return { state, result: LOST };
909
+ if (isTerminalExecutionStatus(state.status)) {
910
+ return { state, result: decodeExecutionOutcome(state.outcome) };
911
+ }
912
+ const { outcome, now } = params;
913
+ const ended = {
914
+ claimedDueAt: null,
915
+ leaseExpiresAt: null,
916
+ unacknowledgedDeliveryCount: 0,
917
+ redeliverAt: null,
918
+ updatedAt: now
919
+ };
920
+ if (outcome.status !== "suspended") {
921
+ const encoded = encodeExecutionOutcome(outcome.status === "completed" ? { status: "completed", result: outcome.result } : { status: "failed", error: outcome.error });
922
+ const terminal = decodeExecutionOutcome(encoded);
923
+ return {
924
+ state: {
925
+ ...state,
926
+ ...ended,
927
+ status: terminal.status,
928
+ outcome: encoded,
929
+ dueAt: null
930
+ },
931
+ result: terminal
932
+ };
933
+ }
934
+ return {
935
+ state: {
936
+ ...state,
937
+ ...ended,
938
+ status: "suspended",
939
+ dueAt: earliest(state.dueAt, outcome.wakeAt ?? null)
940
+ },
941
+ result: { status: "suspended" }
942
+ };
943
+ }
944
+ function earliest(a, b) {
945
+ if (a === null)
946
+ return b;
947
+ if (b === null)
948
+ return a;
949
+ return Math.min(a, b);
950
+ }
951
+ function prepareExecutionAdmission(request) {
952
+ if (typeof request.workflowId !== "string" || request.workflowId === "") {
953
+ return {
954
+ ok: false,
955
+ code: "INVALID_EXECUTION_EVENT",
956
+ message: "workflowId must be a non-empty string"
957
+ };
958
+ }
959
+ if (request.executionId !== undefined && (typeof request.executionId !== "string" || request.executionId === "")) {
960
+ return {
961
+ ok: false,
962
+ code: "INVALID_EXECUTION_EVENT",
963
+ message: "executionId must be a non-empty string"
964
+ };
965
+ }
966
+ try {
967
+ return {
968
+ ok: true,
969
+ executionId: request.executionId ?? crypto.randomUUID(),
970
+ encoded: encodeExecutionEvent(request)
971
+ };
972
+ } catch (err) {
973
+ if (!(err instanceof ExecutionEventEncodingError))
974
+ throw err;
975
+ return { ok: false, code: "INVALID_EXECUTION_EVENT", message: err.message };
976
+ }
977
+ }
978
+ function executionIdConflict(executionId) {
979
+ return {
980
+ status: "rejected",
981
+ code: "EXECUTION_ID_CONFLICT",
982
+ message: `Execution "${executionId}" already exists`
983
+ };
984
+ }
985
+ function executionNotFound(executionId) {
986
+ return {
987
+ status: "rejected",
988
+ code: "EXECUTION_NOT_FOUND",
989
+ message: `Execution "${executionId}" does not exist`
990
+ };
991
+ }
992
+ function prepareExecutionStart(request) {
993
+ const invalid = (message) => ({
994
+ ok: false,
995
+ result: {
996
+ status: "rejected",
997
+ code: "INVALID_START_PARAMETERS",
998
+ message
999
+ }
1000
+ });
1001
+ if (typeof request.workflowId !== "string" || request.workflowId === "") {
1002
+ return invalid("workflowId must be a non-empty string");
1003
+ }
1004
+ let params = null;
1005
+ if (request.params !== undefined) {
1006
+ if (!isPlainObject(request.params)) {
1007
+ return invalid("params must be a plain object");
1008
+ }
1009
+ const encoding = durableValueCodec.encode(request.params);
1010
+ if (encoding.status === "invalid") {
1011
+ return invalid(`params: ${encoding.message}`);
1012
+ }
1013
+ params = encoding.canonical;
1014
+ }
1015
+ return {
1016
+ ok: true,
1017
+ start: {
1018
+ callerExecutionId: request.requestId.executionId,
1019
+ stepKey: request.requestId.stepKey,
1020
+ workflowId: request.workflowId,
1021
+ params
1022
+ }
1023
+ };
1024
+ }
1025
+ function matchStartRequest(recorded, start) {
1026
+ if (recorded.workflowId === start.workflowId && recorded.params === start.params) {
1027
+ return { status: "accepted", executionId: recorded.executionId };
1028
+ }
1029
+ return {
1030
+ status: "rejected",
1031
+ code: "START_REQUEST_MISMATCH",
1032
+ message: `Step "${start.stepKey}" of execution "${start.callerExecutionId}" already started workflow "${recorded.workflowId}" with different parameters or workflow ID`
1033
+ };
1034
+ }
1035
+ function createStartedExecution(params) {
1036
+ const { start, caller, now } = params;
1037
+ const executionId = params.executionId ?? crypto.randomUUID();
1038
+ const target = createExecutionState({
1039
+ executionId,
1040
+ workflowId: start.workflowId,
1041
+ encoded: {
1042
+ params: start.params ?? encodeExecutionEvent({ params: undefined, context: undefined }).params,
1043
+ context: caller.context
1044
+ },
1045
+ now,
1046
+ parent: { executionId: start.callerExecutionId, stepKey: start.stepKey }
1047
+ });
1048
+ return {
1049
+ target,
1050
+ startRequest: {
1051
+ callerExecutionId: start.callerExecutionId,
1052
+ stepKey: start.stepKey,
1053
+ workflowId: start.workflowId,
1054
+ params: start.params,
1055
+ executionId
1056
+ }
1057
+ };
1058
+ }
1059
+ // src/lib/execution-dispatcher.ts
1060
+ var DEFAULT_DISPATCHER_CONCURRENCY = 10;
1061
+ var DEFAULT_ABORT_GRACE_PERIOD = 1000;
1062
+ var ABANDONED = Symbol("abandoned");
1063
+
1064
+ class ExecutionDispatcher {
1065
+ runtime;
1066
+ invoker;
1067
+ logger;
1068
+ pollInterval;
1069
+ keepAlive;
1070
+ concurrency;
1071
+ leaseDuration;
1072
+ renewInterval;
1073
+ abortGracePeriod;
1074
+ running = false;
1075
+ looping = false;
1076
+ timer;
1077
+ inFlight = new Set;
1078
+ claiming = 0;
1079
+ constructor(runtime, options) {
1080
+ this.runtime = runtime;
1081
+ this.invoker = options.invoker;
1082
+ this.logger = options.logger;
1083
+ this.pollInterval = options.pollInterval ?? 10;
1084
+ this.keepAlive = options.keepAlive ?? true;
1085
+ this.concurrency = options.concurrency ?? DEFAULT_DISPATCHER_CONCURRENCY;
1086
+ this.leaseDuration = options.leaseDuration ?? DEFAULT_LEASE_DURATION;
1087
+ this.renewInterval = options.renewInterval ?? this.leaseDuration / 3;
1088
+ this.abortGracePeriod = options.abortGracePeriod ?? DEFAULT_ABORT_GRACE_PERIOD;
1089
+ if (!Number.isInteger(this.concurrency) || this.concurrency < 1) {
1090
+ throw new RangeError("concurrency must be a positive integer");
1091
+ }
1092
+ if (!(this.leaseDuration > 0)) {
1093
+ throw new RangeError("leaseDuration must be positive");
1094
+ }
1095
+ if (!(this.renewInterval > 0 && this.renewInterval < this.leaseDuration)) {
1096
+ throw new RangeError("renewInterval must be positive and shorter than leaseDuration");
1097
+ }
1098
+ if (!(this.abortGracePeriod >= 0)) {
1099
+ throw new RangeError("abortGracePeriod must not be negative");
1100
+ }
1101
+ }
1102
+ start() {
1103
+ if (this.running)
1104
+ return;
1105
+ this.running = true;
1106
+ this.loop();
1107
+ }
1108
+ async stop() {
1109
+ this.running = false;
1110
+ if (this.timer)
1111
+ clearTimeout(this.timer);
1112
+ this.timer = undefined;
1113
+ await this.drain();
1114
+ }
1115
+ get activeDeliveries() {
1116
+ return this.inFlight.size;
1117
+ }
1118
+ async tick() {
1119
+ const slots = this.concurrency - this.inFlight.size - this.claiming;
1120
+ if (slots <= 0)
1121
+ return 0;
1122
+ this.claiming += slots;
1123
+ let leases;
1124
+ try {
1125
+ leases = await this.runtime.executions.claimDue({
1126
+ limit: slots,
1127
+ duration: this.leaseDuration
1128
+ });
1129
+ } finally {
1130
+ this.claiming -= slots;
1131
+ }
1132
+ for (const lease of leases) {
1133
+ const task = this.dispatch(lease).finally(() => {
1134
+ this.inFlight.delete(task);
1135
+ if (this.running && !this.looping)
1136
+ this.schedule(0);
1137
+ });
1138
+ this.inFlight.add(task);
1139
+ }
1140
+ return leases.length;
1141
+ }
1142
+ async drain() {
1143
+ while (this.inFlight.size > 0) {
1144
+ await Promise.allSettled([...this.inFlight]);
1145
+ }
1146
+ }
1147
+ schedule(delay) {
1148
+ if (this.timer)
1149
+ clearTimeout(this.timer);
1150
+ this.timer = setTimeout(() => {
1151
+ this.timer = undefined;
1152
+ this.loop();
1153
+ }, delay);
1154
+ if (!this.keepAlive)
1155
+ this.timer.unref?.();
1156
+ }
1157
+ async loop() {
1158
+ if (!this.running || this.looping)
1159
+ return;
1160
+ this.looping = true;
1161
+ let claimed = 0;
1162
+ try {
1163
+ claimed = await this.tick();
1164
+ } catch (err) {
1165
+ this.logger.error({ err }, "Failed to claim due executions");
1166
+ } finally {
1167
+ this.looping = false;
1168
+ }
1169
+ if (!this.running)
1170
+ return;
1171
+ if (this.inFlight.size + this.claiming >= this.concurrency)
1172
+ return;
1173
+ this.schedule(claimed > 0 ? 0 : this.pollInterval);
1174
+ }
1175
+ async dispatch(lease) {
1176
+ const { executionId } = lease;
1177
+ const { executions } = this.runtime;
1178
+ const renewal = this.renew(lease);
1179
+ try {
1180
+ const record = await executions.get(executionId);
1181
+ if (!record) {
1182
+ this.logger.error({ executionId }, "Claimed execution has no record");
1183
+ return;
1184
+ }
1185
+ if (isTerminalExecutionStatus(record.status)) {
1186
+ this.emit(executionId, record.outcome);
1187
+ return;
1188
+ }
1189
+ let outcome;
1190
+ try {
1191
+ outcome = await this.abandonAfterAbort(this.invoker.deliver({
1192
+ workflowId: record.workflowId,
1193
+ executionId,
1194
+ params: record.params,
1195
+ context: record.context
1196
+ }, { lease, signal: renewal.signal }), lease, renewal.signal);
1197
+ } catch (err) {
1198
+ const current = await renewal.stop();
1199
+ if (!current)
1200
+ return;
1201
+ if (WorkerExitedError.is(err)) {
1202
+ this.logger.error({ executionId, err }, "Worker exited without replying; the lease will expire and the execution will be redelivered");
1203
+ return;
1204
+ }
1205
+ this.logger.error({ executionId, err }, "Delivery failed without an outcome; releasing its lease for redelivery");
1206
+ const released = await executions.release({ lease: current });
1207
+ if (released.status === "lost")
1208
+ this.warnLost(lease);
1209
+ return;
1210
+ }
1211
+ if (outcome === ABANDONED)
1212
+ return;
1213
+ const current = await renewal.stop();
1214
+ if (!current)
1215
+ return;
1216
+ const result = await executions.acknowledge({ lease: current, outcome });
1217
+ if (result.status === "lost") {
1218
+ this.warnLost(lease);
1219
+ return;
1220
+ }
1221
+ this.emit(executionId, result);
1222
+ } catch (err) {
1223
+ this.logger.error({ executionId, err }, "Failed to dispatch execution; the lease will expire and the execution will be redelivered");
1224
+ } finally {
1225
+ await renewal.stop();
1226
+ }
1227
+ }
1228
+ abandonAfterAbort(delivery, lease, signal) {
1229
+ return new Promise((resolve, reject) => {
1230
+ let abandoned = false;
1231
+ let timer;
1232
+ const onAbort = () => {
1233
+ timer = setTimeout(() => {
1234
+ abandoned = true;
1235
+ this.logger.warn({ executionId: lease.executionId, token: lease.token }, "Delivery did not stop within the abort grace period after its lease was lost; abandoning it");
1236
+ resolve(ABANDONED);
1237
+ }, this.abortGracePeriod);
1238
+ if (!this.keepAlive)
1239
+ timer.unref?.();
1240
+ };
1241
+ const settle = () => {
1242
+ clearTimeout(timer);
1243
+ signal.removeEventListener("abort", onAbort);
1244
+ };
1245
+ if (signal.aborted)
1246
+ onAbort();
1247
+ else
1248
+ signal.addEventListener("abort", onAbort, { once: true });
1249
+ delivery.then((value) => {
1250
+ settle();
1251
+ if (abandoned) {
1252
+ this.logger.warn({ executionId: lease.executionId, token: lease.token }, "Abandoned delivery settled late; its outcome is ignored");
1253
+ } else
1254
+ resolve(value);
1255
+ }, (err) => {
1256
+ settle();
1257
+ if (abandoned) {
1258
+ this.logger.warn({ executionId: lease.executionId, token: lease.token, err }, "Abandoned delivery rejected late; ignored");
1259
+ } else
1260
+ reject(err);
1261
+ });
1262
+ });
1263
+ }
1264
+ renew(lease) {
1265
+ const controller = new AbortController;
1266
+ let current = lease;
1267
+ let pending;
1268
+ let stopped = false;
1269
+ const { executions } = this.runtime;
1270
+ const timer = setInterval(() => {
1271
+ if (stopped || pending || !current)
1272
+ return;
1273
+ const renewing = current;
1274
+ pending = executions.renew({ lease: renewing, duration: this.leaseDuration }).then((result) => {
1275
+ if (result.status === "lost") {
1276
+ current = undefined;
1277
+ clearInterval(timer);
1278
+ this.warnLost(lease);
1279
+ controller.abort(new LeaseLostError(lease.executionId));
1280
+ } else if (current) {
1281
+ current = result.lease;
1282
+ }
1283
+ }, (err) => {
1284
+ this.logger.error({ executionId: lease.executionId, err }, "Failed to renew lease");
1285
+ }).finally(() => {
1286
+ pending = undefined;
1287
+ });
1288
+ }, this.renewInterval);
1289
+ if (!this.keepAlive)
1290
+ timer.unref?.();
1291
+ return {
1292
+ signal: controller.signal,
1293
+ async stop() {
1294
+ stopped = true;
1295
+ clearInterval(timer);
1296
+ await pending;
1297
+ return current;
1298
+ }
1299
+ };
1300
+ }
1301
+ warnLost(lease) {
1302
+ this.logger.warn({ executionId: lease.executionId, token: lease.token }, "Lease was lost to another claim; this delivery stops without an outcome");
1303
+ }
1304
+ emit(executionId, result) {
1305
+ if (result.status === "completed") {
1306
+ this.invoker.workflowEndEmitter.emit(executionId, result.result);
1307
+ } else if (result.status === "failed") {
1308
+ this.invoker.workflowEndEmitter.emit(executionId, result.error);
1309
+ }
1310
+ }
1311
+ }
1312
+ // src/lib/wait-for-outcome.ts
1313
+ async function waitForExecutionOutcome(executions, executionId, options) {
1314
+ const pollInterval = options?.pollInterval ?? 25;
1315
+ const emitter = options?.emitter;
1316
+ let wake;
1317
+ const nudge = () => wake?.();
1318
+ emitter?.on(executionId, nudge);
1319
+ try {
1320
+ while (true) {
1321
+ options?.signal?.throwIfAborted();
1322
+ const record = await executions.get(executionId);
1323
+ if (!record)
1324
+ return;
1325
+ if (isTerminalExecutionStatus(record.status))
1326
+ return record.outcome;
1327
+ await new Promise((resolve) => {
1328
+ const timer = setTimeout(resolve, pollInterval);
1329
+ wake = () => {
1330
+ clearTimeout(timer);
1331
+ resolve();
1332
+ };
1333
+ });
1334
+ wake = undefined;
1335
+ }
1336
+ } finally {
1337
+ emitter?.off(executionId, nudge);
1338
+ }
1339
+ }
1
1340
  // src/base/store.ts
2
- function defineStore(name, schema) {
3
- return { name, schema };
1341
+ class StoreValidationError extends Error {
1342
+ constructor(message) {
1343
+ super(message);
1344
+ this.name = "StoreValidationError";
1345
+ }
1346
+ }
1347
+ registerErrorClass("StoreValidationError", StoreValidationError);
1348
+ function defineStore(name, schema, options) {
1349
+ return {
1350
+ name,
1351
+ schema,
1352
+ initial: options.initial
1353
+ };
4
1354
  }
5
1355
 
6
1356
  class StoreClient {
@@ -31,17 +1381,21 @@ class StoreClient {
31
1381
  }
32
1382
 
33
1383
  class CasStoreClient extends StoreClient {
1384
+ createInstanceId() {
1385
+ return crypto.randomUUID();
1386
+ }
1387
+ async getStore(params) {
1388
+ return this.getOrInsertStore(params.definition, params.id);
1389
+ }
34
1390
  async updateStore(params) {
35
1391
  const applied = await this.getAppliedResult(params);
36
1392
  if (applied) {
37
1393
  return applied.result;
38
1394
  }
39
- let snapshot = await this.getStore({
40
- definition: params.definition,
41
- id: params.id
42
- });
1395
+ const stepKey = params.stepId?.stepKey;
1396
+ let snapshot = await this.readOrInitialize(params.definition, params.id, stepKey);
43
1397
  while (true) {
44
- const prepared = await prepareStoreUpdate(params.definition, snapshot, params.updater);
1398
+ const prepared = await prepareStoreUpdate(params.definition, snapshot, params.updater, stepKey);
45
1399
  const result = {
46
1400
  state: cloneStoreState(prepared.nextState),
47
1401
  previousVersion: snapshot.version,
@@ -61,7 +1415,7 @@ class CasStoreClient extends StoreClient {
61
1415
  if (committed.status === "already-applied") {
62
1416
  return committed.result;
63
1417
  }
64
- snapshot = committed.snapshot;
1418
+ snapshot = committed.status === "conflict" ? existing(committed.snapshot) : await this.readOrInitialize(params.definition, params.id, stepKey);
65
1419
  }
66
1420
  }
67
1421
  async updateStoreFrom(params) {
@@ -70,14 +1424,12 @@ class CasStoreClient extends StoreClient {
70
1424
  return applied.result;
71
1425
  }
72
1426
  assertStoreSnapshotInstanceId(params.snapshot);
73
- const current = await this.getStore({
74
- definition: params.definition,
75
- id: params.id
76
- });
1427
+ const stepKey = params.stepId?.stepKey;
1428
+ const current = await this.getOrInsertStore(params.definition, params.id, stepKey);
77
1429
  if (!sameStoreVersion(current, params.snapshot)) {
78
1430
  return storeUpdateConflict(params.snapshot, current);
79
1431
  }
80
- const prepared = await prepareStoreUpdate(params.definition, params.snapshot, params.updater);
1432
+ const prepared = await prepareStoreUpdate(params.definition, params.snapshot, params.updater, stepKey);
81
1433
  const result = {
82
1434
  updated: true,
83
1435
  state: cloneStoreState(prepared.nextState),
@@ -87,7 +1439,7 @@ class CasStoreClient extends StoreClient {
87
1439
  const committed = await this.commitStoreMutation({
88
1440
  definition: params.definition,
89
1441
  id: params.id,
90
- expected: params.snapshot,
1442
+ expected: existing(params.snapshot),
91
1443
  nextState: prepared.nextState,
92
1444
  changedPaths: prepared.changedPaths,
93
1445
  stepId: params.stepId,
@@ -98,22 +1450,63 @@ class CasStoreClient extends StoreClient {
98
1450
  if (committed.status === "already-applied") {
99
1451
  return committed.result;
100
1452
  }
101
- return storeUpdateConflict(params.snapshot, committed.snapshot);
1453
+ return storeUpdateConflict(params.snapshot, committed.status === "conflict" ? committed.snapshot : await this.getOrInsertStore(params.definition, params.id, stepKey));
1454
+ }
1455
+ async deleteStoreFrom(params) {
1456
+ const applied = await this.getAppliedResult(params);
1457
+ if (applied)
1458
+ return applied.result;
1459
+ assertStoreSnapshotInstanceId(params.snapshot);
1460
+ const stepKey = params.stepId?.stepKey;
1461
+ let current = await this.getOrInsertStore(params.definition, params.id, stepKey);
1462
+ while (sameStoreVersion(current, params.snapshot)) {
1463
+ const result = { deleted: true };
1464
+ const committed = await this.commitStoreDeletion({
1465
+ definition: params.definition,
1466
+ id: params.id,
1467
+ expected: params.snapshot,
1468
+ stepId: params.stepId,
1469
+ result
1470
+ });
1471
+ if (committed.status === "committed")
1472
+ return result;
1473
+ if (committed.status === "already-applied") {
1474
+ return committed.result;
1475
+ }
1476
+ current = committed.status === "conflict" ? committed.snapshot : await this.getOrInsertStore(params.definition, params.id, stepKey);
1477
+ }
1478
+ return {
1479
+ deleted: false,
1480
+ reason: "conflict",
1481
+ expectedInstanceId: params.snapshot.instanceId,
1482
+ actualInstanceId: current.instanceId,
1483
+ expectedVersion: params.snapshot.version,
1484
+ actualVersion: current.version
1485
+ };
102
1486
  }
103
1487
  async takeFromStore(params) {
104
1488
  const applied = await this.getAppliedResult(params);
105
1489
  if (applied)
106
1490
  return applied.result;
107
- let snapshot = await this.getStore({
108
- definition: params.definition,
109
- id: params.id
110
- });
1491
+ const stepKey = params.stepId?.stepKey;
1492
+ let snapshot = await this.readOrInitialize(params.definition, params.id, stepKey);
111
1493
  while (true) {
112
1494
  const previousState = cloneStoreState(snapshot.state);
113
1495
  const draft = cloneStoreState(previousState);
114
1496
  const tracked = trackStoreUpdater(draft);
115
1497
  const { result: selectedResult, readPaths } = trackStoreSelector(tracked.draft, params.selector);
116
1498
  if (!isStoreSelectorMatch(selectedResult)) {
1499
+ if (snapshot.created) {
1500
+ const stored = await this.insertStore({
1501
+ definition: params.definition,
1502
+ id: params.id,
1503
+ snapshot: detach(snapshot)
1504
+ });
1505
+ if (stored.instanceId !== snapshot.instanceId) {
1506
+ snapshot = existing(stored);
1507
+ continue;
1508
+ }
1509
+ }
117
1510
  return {
118
1511
  matched: false,
119
1512
  instanceId: snapshot.instanceId,
@@ -127,8 +1520,10 @@ class CasStoreClient extends StoreClient {
127
1520
  definition: params.definition,
128
1521
  previousState,
129
1522
  draft,
130
- tracked
1523
+ tracked,
1524
+ stepKey
131
1525
  });
1526
+ assertDurableStoreValue(selected, stepKey ?? "", `Store take selection for "${params.definition.name}"`);
132
1527
  const selectedSnapshot = cloneStoreState(selected);
133
1528
  const result = {
134
1529
  matched: true,
@@ -150,9 +1545,31 @@ class CasStoreClient extends StoreClient {
150
1545
  if (committed.status === "already-applied") {
151
1546
  return committed.result;
152
1547
  }
153
- snapshot = committed.snapshot;
1548
+ snapshot = committed.status === "conflict" ? existing(committed.snapshot) : await this.readOrInitialize(params.definition, params.id, stepKey);
154
1549
  }
155
1550
  }
1551
+ async readOrInitialize(definition, id, stepKey) {
1552
+ const snapshot = await this.readStore({ definition, id });
1553
+ if (snapshot)
1554
+ return existing(snapshot);
1555
+ const state = await validateStoreState(definition, definition.initial(id), stepKey);
1556
+ return {
1557
+ state,
1558
+ instanceId: this.createInstanceId(),
1559
+ version: 0,
1560
+ created: true
1561
+ };
1562
+ }
1563
+ async getOrInsertStore(definition, id, stepKey) {
1564
+ const snapshot = await this.readOrInitialize(definition, id, stepKey);
1565
+ if (!snapshot.created)
1566
+ return detach(snapshot);
1567
+ return await this.insertStore({
1568
+ definition,
1569
+ id,
1570
+ snapshot: detach(snapshot)
1571
+ });
1572
+ }
156
1573
  getAppliedResult(params) {
157
1574
  return params.stepId ? this.getAppliedStoreStep({
158
1575
  definition: params.definition,
@@ -161,7 +1578,22 @@ class CasStoreClient extends StoreClient {
161
1578
  }) : Promise.resolve(undefined);
162
1579
  }
163
1580
  }
164
- async function prepareStoreUpdate(definition, snapshot, updater) {
1581
+ function existing(snapshot) {
1582
+ return {
1583
+ state: snapshot.state,
1584
+ instanceId: snapshot.instanceId,
1585
+ version: snapshot.version,
1586
+ created: false
1587
+ };
1588
+ }
1589
+ function detach(snapshot) {
1590
+ return {
1591
+ state: snapshot.state,
1592
+ instanceId: snapshot.instanceId,
1593
+ version: snapshot.version
1594
+ };
1595
+ }
1596
+ async function prepareStoreUpdate(definition, snapshot, updater, stepKey) {
165
1597
  const previousState = cloneStoreState(snapshot.state);
166
1598
  const draft = cloneStoreState(previousState);
167
1599
  const tracked = trackStoreUpdater(draft);
@@ -173,12 +1605,13 @@ async function prepareStoreUpdate(definition, snapshot, updater) {
173
1605
  previousState,
174
1606
  draft,
175
1607
  tracked,
176
- replacement: returned
1608
+ replacement: returned,
1609
+ stepKey
177
1610
  });
178
1611
  }
179
1612
  async function finalizeStoreMutation(params) {
180
1613
  const isReplacement = params.replacement !== undefined && params.replacement !== params.draft;
181
- const nextState = await validateStoreState(params.definition, isReplacement ? params.replacement : params.draft);
1614
+ const nextState = await validateStoreState(params.definition, isReplacement ? params.replacement : params.draft, params.stepKey);
182
1615
  const changedPaths = !isReplacement && nextState === params.draft ? params.tracked.writePaths() : diffStorePaths(params.previousState, nextState);
183
1616
  return { nextState, changedPaths };
184
1617
  }
@@ -212,15 +1645,42 @@ function assertSynchronousUpdater(updaterResult) {
212
1645
  throw new Error("Store updaters must be synchronous");
213
1646
  }
214
1647
  }
215
- async function validateStoreState(definition, state) {
1648
+ async function validateStoreState(definition, state, stepKey = "") {
216
1649
  const result = await definition.schema["~standard"].validate(state);
217
1650
  if ("issues" in result && result.issues) {
218
1651
  const messages = result.issues.map((issue) => issue.message).join("; ");
219
- throw new Error(`Invalid store state for "${definition.name}": ${messages}`);
1652
+ throw new StoreValidationError(`Invalid store state for "${definition.name}": ${messages}`);
220
1653
  }
1654
+ assertDurableStoreValue(result.value, stepKey, `Store state for "${definition.name}"`);
221
1655
  return result.value;
222
1656
  }
1657
+ function assertDurableStoreValue(value, stepKey, label) {
1658
+ const encoding = durableValueCodec.encode(value);
1659
+ if (encoding.status === "invalid") {
1660
+ throw new WorkflowValueError("INVALID_STEP_VALUE", stepKey, `${label} is not a durable value: ${encoding.message}`);
1661
+ }
1662
+ }
1663
+ function encodeStoreValue(value) {
1664
+ const encoding = durableValueCodec.encode(value);
1665
+ if (encoding.status === "invalid") {
1666
+ throw new Error(`Store value is not a durable value: ${encoding.message}`);
1667
+ }
1668
+ return encoding.canonical;
1669
+ }
1670
+ function decodeStoreValue(canonical) {
1671
+ const decoding = durableValueCodec.decode(canonical);
1672
+ if (decoding.status === "invalid") {
1673
+ throw new Error(`Invalid store value encoding: ${decoding.message}`);
1674
+ }
1675
+ return decoding.value;
1676
+ }
223
1677
  function cloneStoreState(state) {
1678
+ const encoding = durableValueCodec.encode(state);
1679
+ if (encoding.status === "encoded") {
1680
+ const decoding = durableValueCodec.decode(encoding.canonical);
1681
+ if (decoding.status === "decoded")
1682
+ return decoding.value;
1683
+ }
224
1684
  if (typeof structuredClone === "function") {
225
1685
  try {
226
1686
  return structuredClone(state);
@@ -310,29 +1770,29 @@ function trackStoreUpdater(target) {
310
1770
  if (cached)
311
1771
  return cached;
312
1772
  const proxy = new Proxy(value, {
313
- get(target2, property, receiver) {
1773
+ get(target, property, receiver) {
314
1774
  if (typeof property === "symbol") {
315
1775
  if (property === TRACKING_TARGET)
316
- return target2;
317
- return Reflect.get(target2, property, receiver);
1776
+ return target;
1777
+ return Reflect.get(target, property, receiver);
318
1778
  }
319
- const child = Reflect.get(target2, property);
1779
+ const child = Reflect.get(target, property);
320
1780
  return track(child, [...path, arrayKeyToPathSegment(property)]);
321
1781
  },
322
- set(target2, property, value2) {
1782
+ set(target, property, value) {
323
1783
  record(path, property);
324
- return Reflect.set(target2, property, unwrapTrackedValue(value2));
1784
+ return Reflect.set(target, property, unwrapTrackedValue(value));
325
1785
  },
326
- deleteProperty(target2, property) {
1786
+ deleteProperty(target, property) {
327
1787
  record(path, property);
328
- return Reflect.deleteProperty(target2, property);
1788
+ return Reflect.deleteProperty(target, property);
329
1789
  },
330
- defineProperty(target2, property, descriptor) {
1790
+ defineProperty(target, property, descriptor) {
331
1791
  record(path, property);
332
1792
  if ("value" in descriptor) {
333
1793
  descriptor = { ...descriptor, value: unwrapTrackedValue(descriptor.value) };
334
1794
  }
335
- return Reflect.defineProperty(target2, property, descriptor);
1795
+ return Reflect.defineProperty(target, property, descriptor);
336
1796
  }
337
1797
  });
338
1798
  proxies.set(value, proxy);
@@ -391,9 +1851,12 @@ class StepResult extends StepResponse {
391
1851
  static type = "step-result";
392
1852
  type = "step-result";
393
1853
  result;
394
- constructor(result) {
1854
+ schema;
1855
+ constructor(result, schema) {
395
1856
  super();
396
1857
  this.result = result;
1858
+ if (schema !== undefined)
1859
+ this.schema = schema;
397
1860
  }
398
1861
  }
399
1862
 
@@ -410,10 +1873,10 @@ class WorkflowResult extends StepResponse {
410
1873
  class WorkflowDelay extends StepResponse {
411
1874
  static type = "workflow-delay";
412
1875
  type = "workflow-delay";
413
- resumeIn;
414
- constructor(resumeIn) {
1876
+ wakeAt;
1877
+ constructor(wakeAt) {
415
1878
  super();
416
- this.resumeIn = resumeIn;
1879
+ this.wakeAt = wakeAt;
417
1880
  }
418
1881
  }
419
1882
 
@@ -425,6 +1888,16 @@ class StepStoreWait extends StepResponse {
425
1888
  }
426
1889
  }
427
1890
 
1891
+ class StepEffectWait extends StepResponse {
1892
+ static type = "effect-wait";
1893
+ type = "effect-wait";
1894
+ wakeAt;
1895
+ constructor(wakeAt) {
1896
+ super();
1897
+ this.wakeAt = wakeAt;
1898
+ }
1899
+ }
1900
+
428
1901
  class WorkflowRestart extends StepResponse {
429
1902
  static type = "workflow-restart";
430
1903
  type = "workflow-restart";
@@ -452,54 +1925,66 @@ class StepError extends StepResponse {
452
1925
  class StepDelay extends StepResponse {
453
1926
  static type = "step-delay";
454
1927
  type = "step-delay";
455
- resumeIn;
456
- constructor(resumeIn) {
1928
+ wakeAt;
1929
+ constructor(wakeAt) {
457
1930
  super();
458
- this.resumeIn = resumeIn;
1931
+ this.wakeAt = wakeAt;
459
1932
  }
460
1933
  }
461
1934
  // src/lib/workflow-runner.ts
462
1935
  class WorkflowRunner {
463
- heapClient;
464
- schedulerClient;
465
- storeClient;
1936
+ runtime;
466
1937
  router;
467
- constructor(params) {
468
- this.heapClient = params.heapClient;
469
- this.schedulerClient = params.schedulerClient;
470
- this.storeClient = params.storeClient;
471
- this.router = params.router;
1938
+ logger;
1939
+ constructor(runtime, options) {
1940
+ this.runtime = runtime;
1941
+ this.router = options.router;
1942
+ this.logger = options.logger;
472
1943
  }
473
- run = async (event, logger) => {
1944
+ run = async (event, options) => {
1945
+ const { lease, signal, logger = this.logger } = options;
474
1946
  const workflow = this.router[event.workflowId];
475
1947
  if (!workflow) {
476
- throw new Error(`No workflow registered for "${event.workflowId}"`);
1948
+ return {
1949
+ status: "failed",
1950
+ error: new Error(`No workflow registered for "${event.workflowId}"`)
1951
+ };
477
1952
  }
1953
+ let response;
478
1954
  try {
479
- const response = await this.runWorkflows({
1955
+ response = await this.runWorkflows({
480
1956
  workflow,
481
1957
  logger,
482
- event
1958
+ event,
1959
+ lease,
1960
+ signal
483
1961
  });
484
- switch (response.type) {
485
- case "workflow-result":
486
- return response;
487
- case "workflow-delay":
488
- this.schedulerClient.requestWakeUp(event, response.resumeIn);
489
- break;
490
- case "store-wait":
491
- break;
492
- }
493
1962
  } catch (err) {
494
- throw err;
1963
+ if (InfrastructureError.is(err))
1964
+ throw err;
1965
+ return { status: "failed", error: toError(err) };
1966
+ }
1967
+ switch (response.type) {
1968
+ case "workflow-result":
1969
+ return { status: "completed", result: response.result };
1970
+ case "step-delay":
1971
+ case "effect-wait":
1972
+ return { status: "suspended", wakeAt: response.wakeAt };
1973
+ case "store-wait":
1974
+ return { status: "suspended" };
495
1975
  }
496
1976
  };
497
1977
  async runWorkflows(params) {
498
- const { workflow, event, logger } = params;
1978
+ const { workflow, event, lease, signal, logger } = params;
499
1979
  const workflowIterator = workflow({
500
1980
  event,
501
- heapClient: this.heapClient,
502
- storeClient: this.storeClient,
1981
+ lease,
1982
+ signal,
1983
+ heapClient: this.runtime.heap,
1984
+ storeClient: this.runtime.stores,
1985
+ executionClient: this.runtime.executions,
1986
+ effectClient: this.runtime.effects,
1987
+ clock: this.runtime.clock,
503
1988
  logger
504
1989
  });
505
1990
  const iteratorResult = await workflowIterator.next();
@@ -508,117 +1993,107 @@ class WorkflowRunner {
508
1993
  return stageResponse;
509
1994
  }
510
1995
  if (stageResponse instanceof StepDelay) {
511
- return new WorkflowDelay(stageResponse.resumeIn - Date.now());
1996
+ return stageResponse;
512
1997
  }
513
- if (stageResponse instanceof StepStoreWait) {
1998
+ if (stageResponse instanceof StepEffectWait || stageResponse instanceof StepStoreWait) {
514
1999
  return stageResponse;
515
2000
  }
516
2001
  throw new Error("Workflow runner critical error. This is likely because your worklow and worker are referencing different versions of @yieldstar/core.");
517
2002
  }
518
2003
  }
519
- // src/utils/error.ts
520
- function errorWithOriginalStack(error, fn) {
521
- const err = new Error(error.message);
522
- Error.captureStackTrace(err, fn);
523
- err.stack = error.stack;
524
- throw err;
525
- }
526
-
527
- // src/utils/map.ts
528
- class ReadOnlyMap {
529
- sourceMap;
530
- constructor(sourceMap) {
531
- if (sourceMap instanceof Map) {
532
- this.sourceMap = sourceMap;
533
- } else if (typeof sourceMap === "object" && sourceMap !== null) {
534
- this.sourceMap = new Map(Object.entries(sourceMap));
535
- } else {
536
- this.sourceMap = new Map;
537
- }
538
- }
539
- get(key) {
540
- return this.sourceMap.get(key);
541
- }
542
- has(key) {
543
- return this.sourceMap.has(key);
544
- }
545
- get size() {
546
- return this.sourceMap.size;
547
- }
548
- entries() {
549
- return this.sourceMap.entries();
550
- }
551
- keys() {
552
- return this.sourceMap.keys();
553
- }
554
- values() {
555
- return this.sourceMap.values();
556
- }
557
- forEach(callbackfn, thisArg) {
558
- return this.sourceMap.forEach(callbackfn, thisArg);
559
- }
560
- [Symbol.iterator]() {
561
- return this.sourceMap[Symbol.iterator]();
562
- }
563
- }
564
-
565
- class FreezableMap extends Map {
566
- frozen = false;
567
- freeze() {
568
- this.frozen = true;
569
- }
570
- isFrozen() {
571
- return this.frozen;
572
- }
573
- set(key, value) {
574
- if (this.frozen) {
575
- const error = new Error("Cannot call set on a frozen map");
576
- throw errorWithOriginalStack(error, this.set);
577
- }
578
- return super.set(key, value);
579
- }
580
- delete(key) {
581
- if (this.frozen) {
582
- const error = new Error("Cannot call delete on a frozen map");
583
- throw errorWithOriginalStack(error, this.delete);
584
- }
585
- return super.delete(key);
586
- }
587
- clear() {
588
- if (this.frozen) {
589
- const error = new Error("Cannot call clear on a frozen map");
590
- throw errorWithOriginalStack(error, this.clear);
591
- }
592
- return super.clear();
593
- }
2004
+ function toError(value) {
2005
+ return value instanceof Error ? value : new Error(String(value));
594
2006
  }
595
2007
  export {
596
- validateStoreState,
597
- unwrapTrackedValue,
598
- trackStoreUpdater,
599
- trackStoreSelector,
600
- storePathsIntersect,
601
- isStoreSelectorMatch,
602
- errorWithOriginalStack,
603
- diffStorePaths,
604
- defineStore,
605
- cloneStoreState,
606
- assertSynchronousUpdater,
607
- assertSynchronousClaim,
608
- WorkflowRunner,
609
- WorkflowResult,
610
- WorkflowRestart,
611
- WorkflowDelay,
612
- StoreClient,
613
- StepStoreWait,
614
- StepResult,
615
- StepResponse,
616
- StepKey,
617
- StepInvalid,
618
- StepError,
619
- StepDelay,
620
- StepCacheCheck,
621
- ReadOnlyMap,
2008
+ CasStoreClient,
2009
+ DEFAULT_ABORT_GRACE_PERIOD,
2010
+ DEFAULT_DISPATCHER_CONCURRENCY,
2011
+ DEFAULT_LEASE_DURATION,
2012
+ DurableValueError,
2013
+ EffectClientInvariantError,
2014
+ EffectDeliveryError,
2015
+ ExecutionAdmissionError,
2016
+ ExecutionDispatcher,
2017
+ ExecutionEventEncodingError,
622
2018
  FreezableMap,
623
- CasStoreClient
2019
+ InfrastructureError,
2020
+ LeaseLostError,
2021
+ REDELIVERY_DELAY_BASE,
2022
+ REDELIVERY_DELAY_MAX,
2023
+ ReadOnlyMap,
2024
+ Runtime,
2025
+ StepCacheCheck,
2026
+ StepConflictError,
2027
+ StepDelay,
2028
+ StepEffectWait,
2029
+ StepError,
2030
+ StepInvalid,
2031
+ StepKey,
2032
+ StepResponse,
2033
+ StepResult,
2034
+ StepStoreWait,
2035
+ StoreClient,
2036
+ StoreValidationError,
2037
+ WORKFLOW_RESULT_STEP_KEY,
2038
+ WorkerExitedError,
2039
+ WorkflowDelay,
2040
+ WorkflowEffectError,
2041
+ WorkflowRestart,
2042
+ WorkflowResult,
2043
+ WorkflowRunner,
2044
+ WorkflowStartError,
2045
+ WorkflowValueError,
2046
+ acknowledgeExecution,
2047
+ assertSynchronousClaim,
2048
+ assertSynchronousUpdater,
2049
+ claimExecution,
2050
+ cloneStoreState,
2051
+ createEffectState,
2052
+ createExecutionState,
2053
+ createStartedExecution,
2054
+ decodeExecutionEvent,
2055
+ decodeExecutionOutcome,
2056
+ decodeParsedValue,
2057
+ decodeStoreValue,
2058
+ defineStore,
2059
+ diffStorePaths,
2060
+ durableValueCodec,
2061
+ effectNotFound,
2062
+ encodeExecutionEvent,
2063
+ encodeExecutionOutcome,
2064
+ encodeStoreValue,
2065
+ encodeThrownValue,
2066
+ errorWithOriginalStack,
2067
+ executionIdConflict,
2068
+ executionNotFound,
2069
+ executionStatusAt,
2070
+ isExecutionClaimable,
2071
+ isLeaseCurrent,
2072
+ isLeaseLost,
2073
+ isStoreSelectorMatch,
2074
+ isTerminalExecutionStatus,
2075
+ matchEffectBegin,
2076
+ matchStartRequest,
2077
+ nextClaimAt,
2078
+ prepareEffectBegin,
2079
+ prepareEffectSettle,
2080
+ prepareEffectWatch,
2081
+ prepareExecutionAdmission,
2082
+ prepareExecutionStart,
2083
+ redeliveryDelay,
2084
+ registerErrorClass,
2085
+ releaseExecutionLease,
2086
+ renewExecutionLease,
2087
+ requestExecutionWake,
2088
+ settleEffect,
2089
+ storePathsIntersect,
2090
+ toEffectRecord,
2091
+ toExecutionRecord,
2092
+ trackStoreSelector,
2093
+ trackStoreUpdater,
2094
+ unacknowledgedDeliveryCountAt,
2095
+ unwrapTrackedValue,
2096
+ validateEffectRequest,
2097
+ validateStoreState,
2098
+ waitForExecutionOutcome
624
2099
  };