@yieldstar/core 0.5.0 → 0.6.1

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 +255 -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 +1653 -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 +112 -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 +315 -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 +138 -3
  57. package/src/lib/effect-state.ts +254 -0
  58. package/src/lib/execution-dispatcher.ts +408 -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,1362 @@
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
+ filter;
1075
+ running = false;
1076
+ looping = false;
1077
+ timer;
1078
+ inFlight = new Set;
1079
+ claiming = 0;
1080
+ constructor(runtime, options) {
1081
+ this.runtime = runtime;
1082
+ this.invoker = options.invoker;
1083
+ this.logger = options.logger;
1084
+ this.pollInterval = options.pollInterval ?? 10;
1085
+ this.keepAlive = options.keepAlive ?? true;
1086
+ this.concurrency = options.concurrency ?? DEFAULT_DISPATCHER_CONCURRENCY;
1087
+ this.leaseDuration = options.leaseDuration ?? DEFAULT_LEASE_DURATION;
1088
+ this.renewInterval = options.renewInterval ?? this.leaseDuration / 3;
1089
+ this.abortGracePeriod = options.abortGracePeriod ?? DEFAULT_ABORT_GRACE_PERIOD;
1090
+ this.filter = options.filter;
1091
+ if (!Number.isInteger(this.concurrency) || this.concurrency < 1) {
1092
+ throw new RangeError("concurrency must be a positive integer");
1093
+ }
1094
+ if (!(this.leaseDuration > 0)) {
1095
+ throw new RangeError("leaseDuration must be positive");
1096
+ }
1097
+ if (!(this.renewInterval > 0 && this.renewInterval < this.leaseDuration)) {
1098
+ throw new RangeError("renewInterval must be positive and shorter than leaseDuration");
1099
+ }
1100
+ if (!(this.abortGracePeriod >= 0)) {
1101
+ throw new RangeError("abortGracePeriod must not be negative");
1102
+ }
1103
+ if (this.filter?.executionIds?.length === 0) {
1104
+ throw new RangeError("filter.executionIds must not be empty");
1105
+ }
1106
+ }
1107
+ start() {
1108
+ if (this.running)
1109
+ return;
1110
+ this.running = true;
1111
+ this.loop();
1112
+ }
1113
+ async stop() {
1114
+ this.running = false;
1115
+ if (this.timer)
1116
+ clearTimeout(this.timer);
1117
+ this.timer = undefined;
1118
+ await this.drain();
1119
+ }
1120
+ get activeDeliveries() {
1121
+ return this.inFlight.size;
1122
+ }
1123
+ async tick() {
1124
+ const slots = this.concurrency - this.inFlight.size - this.claiming;
1125
+ if (slots <= 0)
1126
+ return 0;
1127
+ this.claiming += slots;
1128
+ let leases;
1129
+ try {
1130
+ leases = await this.runtime.executions.claimDue({
1131
+ limit: slots,
1132
+ duration: this.leaseDuration,
1133
+ filter: this.filter
1134
+ });
1135
+ } finally {
1136
+ this.claiming -= slots;
1137
+ }
1138
+ for (const lease of leases) {
1139
+ const task = this.dispatch(lease).finally(() => {
1140
+ this.inFlight.delete(task);
1141
+ if (this.running && !this.looping)
1142
+ this.schedule(0);
1143
+ });
1144
+ this.inFlight.add(task);
1145
+ }
1146
+ return leases.length;
1147
+ }
1148
+ async drain() {
1149
+ while (this.inFlight.size > 0) {
1150
+ await Promise.allSettled([...this.inFlight]);
1151
+ }
1152
+ }
1153
+ schedule(delay) {
1154
+ if (this.timer)
1155
+ clearTimeout(this.timer);
1156
+ this.timer = setTimeout(() => {
1157
+ this.timer = undefined;
1158
+ this.loop();
1159
+ }, delay);
1160
+ if (!this.keepAlive)
1161
+ this.timer.unref?.();
1162
+ }
1163
+ async loop() {
1164
+ if (!this.running || this.looping)
1165
+ return;
1166
+ this.looping = true;
1167
+ let claimed = 0;
1168
+ try {
1169
+ claimed = await this.tick();
1170
+ } catch (err) {
1171
+ this.logger.error({ err }, "Failed to claim due executions");
1172
+ } finally {
1173
+ this.looping = false;
1174
+ }
1175
+ if (!this.running)
1176
+ return;
1177
+ if (this.inFlight.size + this.claiming >= this.concurrency)
1178
+ return;
1179
+ this.schedule(claimed > 0 ? 0 : this.pollInterval);
1180
+ }
1181
+ async dispatch(lease) {
1182
+ const { executionId } = lease;
1183
+ const { executions } = this.runtime;
1184
+ const renewal = this.renew(lease);
1185
+ try {
1186
+ const record = await executions.get(executionId);
1187
+ if (!record) {
1188
+ this.logger.error({ executionId }, "Claimed execution has no record");
1189
+ return;
1190
+ }
1191
+ if (isTerminalExecutionStatus(record.status)) {
1192
+ this.emit(executionId, record.outcome);
1193
+ return;
1194
+ }
1195
+ let outcome;
1196
+ try {
1197
+ outcome = await this.abandonAfterAbort(this.invoker.deliver({
1198
+ workflowId: record.workflowId,
1199
+ executionId,
1200
+ params: record.params,
1201
+ context: record.context
1202
+ }, { lease, signal: renewal.signal }), lease, renewal.signal);
1203
+ } catch (err) {
1204
+ const current = await renewal.stop();
1205
+ if (!current)
1206
+ return;
1207
+ if (WorkerExitedError.is(err)) {
1208
+ this.logger.error({ executionId, err }, "Worker exited without replying; the lease will expire and the execution will be redelivered");
1209
+ return;
1210
+ }
1211
+ this.logger.error({ executionId, err }, "Delivery failed without an outcome; releasing its lease for redelivery");
1212
+ const released = await executions.release({ lease: current });
1213
+ if (released.status === "lost")
1214
+ this.warnLost(lease);
1215
+ return;
1216
+ }
1217
+ if (outcome === ABANDONED)
1218
+ return;
1219
+ const current = await renewal.stop();
1220
+ if (!current)
1221
+ return;
1222
+ const result = await executions.acknowledge({ lease: current, outcome });
1223
+ if (result.status === "lost") {
1224
+ this.warnLost(lease);
1225
+ return;
1226
+ }
1227
+ this.emit(executionId, result);
1228
+ } catch (err) {
1229
+ this.logger.error({ executionId, err }, "Failed to dispatch execution; the lease will expire and the execution will be redelivered");
1230
+ } finally {
1231
+ await renewal.stop();
1232
+ }
1233
+ }
1234
+ abandonAfterAbort(delivery, lease, signal) {
1235
+ return new Promise((resolve, reject) => {
1236
+ let abandoned = false;
1237
+ let timer;
1238
+ const onAbort = () => {
1239
+ timer = setTimeout(() => {
1240
+ abandoned = true;
1241
+ 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");
1242
+ resolve(ABANDONED);
1243
+ }, this.abortGracePeriod);
1244
+ if (!this.keepAlive)
1245
+ timer.unref?.();
1246
+ };
1247
+ const settle = () => {
1248
+ clearTimeout(timer);
1249
+ signal.removeEventListener("abort", onAbort);
1250
+ };
1251
+ if (signal.aborted)
1252
+ onAbort();
1253
+ else
1254
+ signal.addEventListener("abort", onAbort, { once: true });
1255
+ delivery.then((value) => {
1256
+ settle();
1257
+ if (abandoned) {
1258
+ this.logger.warn({ executionId: lease.executionId, token: lease.token }, "Abandoned delivery settled late; its outcome is ignored");
1259
+ } else
1260
+ resolve(value);
1261
+ }, (err) => {
1262
+ settle();
1263
+ if (abandoned) {
1264
+ this.logger.warn({ executionId: lease.executionId, token: lease.token, err }, "Abandoned delivery rejected late; ignored");
1265
+ } else
1266
+ reject(err);
1267
+ });
1268
+ });
1269
+ }
1270
+ renew(lease) {
1271
+ const controller = new AbortController;
1272
+ let current = lease;
1273
+ let pending;
1274
+ let stopped = false;
1275
+ const { executions } = this.runtime;
1276
+ const timer = setInterval(() => {
1277
+ if (stopped || pending || !current)
1278
+ return;
1279
+ const renewing = current;
1280
+ pending = executions.renew({ lease: renewing, duration: this.leaseDuration }).then((result) => {
1281
+ if (result.status === "lost") {
1282
+ current = undefined;
1283
+ clearInterval(timer);
1284
+ this.warnLost(lease);
1285
+ controller.abort(new LeaseLostError(lease.executionId));
1286
+ } else if (current) {
1287
+ current = result.lease;
1288
+ }
1289
+ }, (err) => {
1290
+ this.logger.error({ executionId: lease.executionId, err }, "Failed to renew lease");
1291
+ }).finally(() => {
1292
+ pending = undefined;
1293
+ });
1294
+ }, this.renewInterval);
1295
+ if (!this.keepAlive)
1296
+ timer.unref?.();
1297
+ return {
1298
+ signal: controller.signal,
1299
+ async stop() {
1300
+ stopped = true;
1301
+ clearInterval(timer);
1302
+ await pending;
1303
+ return current;
1304
+ }
1305
+ };
1306
+ }
1307
+ warnLost(lease) {
1308
+ this.logger.warn({ executionId: lease.executionId, token: lease.token }, "Lease was lost to another claim; this delivery stops without an outcome");
1309
+ }
1310
+ emit(executionId, result) {
1311
+ if (result.status === "completed") {
1312
+ this.invoker.workflowEndEmitter.emit(executionId, result.result);
1313
+ } else if (result.status === "failed") {
1314
+ this.invoker.workflowEndEmitter.emit(executionId, result.error);
1315
+ }
1316
+ }
1317
+ }
1318
+ // src/lib/wait-for-outcome.ts
1319
+ async function waitForExecutionOutcome(executions, executionId, options) {
1320
+ const pollInterval = options?.pollInterval ?? 25;
1321
+ const emitter = options?.emitter;
1322
+ let wake;
1323
+ const nudge = () => wake?.();
1324
+ emitter?.on(executionId, nudge);
1325
+ try {
1326
+ while (true) {
1327
+ options?.signal?.throwIfAborted();
1328
+ const record = await executions.get(executionId);
1329
+ if (!record)
1330
+ return;
1331
+ if (isTerminalExecutionStatus(record.status))
1332
+ return record.outcome;
1333
+ await new Promise((resolve) => {
1334
+ const timer = setTimeout(resolve, pollInterval);
1335
+ wake = () => {
1336
+ clearTimeout(timer);
1337
+ resolve();
1338
+ };
1339
+ });
1340
+ wake = undefined;
1341
+ }
1342
+ } finally {
1343
+ emitter?.off(executionId, nudge);
1344
+ }
1345
+ }
1
1346
  // src/base/store.ts
2
- function defineStore(name, schema) {
3
- return { name, schema };
1347
+ class StoreValidationError extends Error {
1348
+ constructor(message) {
1349
+ super(message);
1350
+ this.name = "StoreValidationError";
1351
+ }
1352
+ }
1353
+ registerErrorClass("StoreValidationError", StoreValidationError);
1354
+ function defineStore(name, schema, options) {
1355
+ return {
1356
+ name,
1357
+ schema,
1358
+ initial: options.initial
1359
+ };
4
1360
  }
5
1361
 
6
1362
  class StoreClient {
@@ -31,17 +1387,21 @@ class StoreClient {
31
1387
  }
32
1388
 
33
1389
  class CasStoreClient extends StoreClient {
1390
+ createInstanceId() {
1391
+ return crypto.randomUUID();
1392
+ }
1393
+ async getStore(params) {
1394
+ return this.getOrInsertStore(params.definition, params.id);
1395
+ }
34
1396
  async updateStore(params) {
35
1397
  const applied = await this.getAppliedResult(params);
36
1398
  if (applied) {
37
1399
  return applied.result;
38
1400
  }
39
- let snapshot = await this.getStore({
40
- definition: params.definition,
41
- id: params.id
42
- });
1401
+ const stepKey = params.stepId?.stepKey;
1402
+ let snapshot = await this.readOrInitialize(params.definition, params.id, stepKey);
43
1403
  while (true) {
44
- const prepared = await prepareStoreUpdate(params.definition, snapshot, params.updater);
1404
+ const prepared = await prepareStoreUpdate(params.definition, snapshot, params.updater, stepKey);
45
1405
  const result = {
46
1406
  state: cloneStoreState(prepared.nextState),
47
1407
  previousVersion: snapshot.version,
@@ -61,7 +1421,7 @@ class CasStoreClient extends StoreClient {
61
1421
  if (committed.status === "already-applied") {
62
1422
  return committed.result;
63
1423
  }
64
- snapshot = committed.snapshot;
1424
+ snapshot = committed.status === "conflict" ? existing(committed.snapshot) : await this.readOrInitialize(params.definition, params.id, stepKey);
65
1425
  }
66
1426
  }
67
1427
  async updateStoreFrom(params) {
@@ -70,14 +1430,12 @@ class CasStoreClient extends StoreClient {
70
1430
  return applied.result;
71
1431
  }
72
1432
  assertStoreSnapshotInstanceId(params.snapshot);
73
- const current = await this.getStore({
74
- definition: params.definition,
75
- id: params.id
76
- });
1433
+ const stepKey = params.stepId?.stepKey;
1434
+ const current = await this.getOrInsertStore(params.definition, params.id, stepKey);
77
1435
  if (!sameStoreVersion(current, params.snapshot)) {
78
1436
  return storeUpdateConflict(params.snapshot, current);
79
1437
  }
80
- const prepared = await prepareStoreUpdate(params.definition, params.snapshot, params.updater);
1438
+ const prepared = await prepareStoreUpdate(params.definition, params.snapshot, params.updater, stepKey);
81
1439
  const result = {
82
1440
  updated: true,
83
1441
  state: cloneStoreState(prepared.nextState),
@@ -87,7 +1445,7 @@ class CasStoreClient extends StoreClient {
87
1445
  const committed = await this.commitStoreMutation({
88
1446
  definition: params.definition,
89
1447
  id: params.id,
90
- expected: params.snapshot,
1448
+ expected: existing(params.snapshot),
91
1449
  nextState: prepared.nextState,
92
1450
  changedPaths: prepared.changedPaths,
93
1451
  stepId: params.stepId,
@@ -98,22 +1456,63 @@ class CasStoreClient extends StoreClient {
98
1456
  if (committed.status === "already-applied") {
99
1457
  return committed.result;
100
1458
  }
101
- return storeUpdateConflict(params.snapshot, committed.snapshot);
1459
+ return storeUpdateConflict(params.snapshot, committed.status === "conflict" ? committed.snapshot : await this.getOrInsertStore(params.definition, params.id, stepKey));
1460
+ }
1461
+ async deleteStoreFrom(params) {
1462
+ const applied = await this.getAppliedResult(params);
1463
+ if (applied)
1464
+ return applied.result;
1465
+ assertStoreSnapshotInstanceId(params.snapshot);
1466
+ const stepKey = params.stepId?.stepKey;
1467
+ let current = await this.getOrInsertStore(params.definition, params.id, stepKey);
1468
+ while (sameStoreVersion(current, params.snapshot)) {
1469
+ const result = { deleted: true };
1470
+ const committed = await this.commitStoreDeletion({
1471
+ definition: params.definition,
1472
+ id: params.id,
1473
+ expected: params.snapshot,
1474
+ stepId: params.stepId,
1475
+ result
1476
+ });
1477
+ if (committed.status === "committed")
1478
+ return result;
1479
+ if (committed.status === "already-applied") {
1480
+ return committed.result;
1481
+ }
1482
+ current = committed.status === "conflict" ? committed.snapshot : await this.getOrInsertStore(params.definition, params.id, stepKey);
1483
+ }
1484
+ return {
1485
+ deleted: false,
1486
+ reason: "conflict",
1487
+ expectedInstanceId: params.snapshot.instanceId,
1488
+ actualInstanceId: current.instanceId,
1489
+ expectedVersion: params.snapshot.version,
1490
+ actualVersion: current.version
1491
+ };
102
1492
  }
103
1493
  async takeFromStore(params) {
104
1494
  const applied = await this.getAppliedResult(params);
105
1495
  if (applied)
106
1496
  return applied.result;
107
- let snapshot = await this.getStore({
108
- definition: params.definition,
109
- id: params.id
110
- });
1497
+ const stepKey = params.stepId?.stepKey;
1498
+ let snapshot = await this.readOrInitialize(params.definition, params.id, stepKey);
111
1499
  while (true) {
112
1500
  const previousState = cloneStoreState(snapshot.state);
113
1501
  const draft = cloneStoreState(previousState);
114
1502
  const tracked = trackStoreUpdater(draft);
115
1503
  const { result: selectedResult, readPaths } = trackStoreSelector(tracked.draft, params.selector);
116
1504
  if (!isStoreSelectorMatch(selectedResult)) {
1505
+ if (snapshot.created) {
1506
+ const stored = await this.insertStore({
1507
+ definition: params.definition,
1508
+ id: params.id,
1509
+ snapshot: detach(snapshot)
1510
+ });
1511
+ if (stored.instanceId !== snapshot.instanceId) {
1512
+ snapshot = existing(stored);
1513
+ continue;
1514
+ }
1515
+ }
117
1516
  return {
118
1517
  matched: false,
119
1518
  instanceId: snapshot.instanceId,
@@ -127,8 +1526,10 @@ class CasStoreClient extends StoreClient {
127
1526
  definition: params.definition,
128
1527
  previousState,
129
1528
  draft,
130
- tracked
1529
+ tracked,
1530
+ stepKey
131
1531
  });
1532
+ assertDurableStoreValue(selected, stepKey ?? "", `Store take selection for "${params.definition.name}"`);
132
1533
  const selectedSnapshot = cloneStoreState(selected);
133
1534
  const result = {
134
1535
  matched: true,
@@ -150,9 +1551,31 @@ class CasStoreClient extends StoreClient {
150
1551
  if (committed.status === "already-applied") {
151
1552
  return committed.result;
152
1553
  }
153
- snapshot = committed.snapshot;
1554
+ snapshot = committed.status === "conflict" ? existing(committed.snapshot) : await this.readOrInitialize(params.definition, params.id, stepKey);
154
1555
  }
155
1556
  }
1557
+ async readOrInitialize(definition, id, stepKey) {
1558
+ const snapshot = await this.readStore({ definition, id });
1559
+ if (snapshot)
1560
+ return existing(snapshot);
1561
+ const state = await validateStoreState(definition, definition.initial(id), stepKey);
1562
+ return {
1563
+ state,
1564
+ instanceId: this.createInstanceId(),
1565
+ version: 0,
1566
+ created: true
1567
+ };
1568
+ }
1569
+ async getOrInsertStore(definition, id, stepKey) {
1570
+ const snapshot = await this.readOrInitialize(definition, id, stepKey);
1571
+ if (!snapshot.created)
1572
+ return detach(snapshot);
1573
+ return await this.insertStore({
1574
+ definition,
1575
+ id,
1576
+ snapshot: detach(snapshot)
1577
+ });
1578
+ }
156
1579
  getAppliedResult(params) {
157
1580
  return params.stepId ? this.getAppliedStoreStep({
158
1581
  definition: params.definition,
@@ -161,7 +1584,22 @@ class CasStoreClient extends StoreClient {
161
1584
  }) : Promise.resolve(undefined);
162
1585
  }
163
1586
  }
164
- async function prepareStoreUpdate(definition, snapshot, updater) {
1587
+ function existing(snapshot) {
1588
+ return {
1589
+ state: snapshot.state,
1590
+ instanceId: snapshot.instanceId,
1591
+ version: snapshot.version,
1592
+ created: false
1593
+ };
1594
+ }
1595
+ function detach(snapshot) {
1596
+ return {
1597
+ state: snapshot.state,
1598
+ instanceId: snapshot.instanceId,
1599
+ version: snapshot.version
1600
+ };
1601
+ }
1602
+ async function prepareStoreUpdate(definition, snapshot, updater, stepKey) {
165
1603
  const previousState = cloneStoreState(snapshot.state);
166
1604
  const draft = cloneStoreState(previousState);
167
1605
  const tracked = trackStoreUpdater(draft);
@@ -173,12 +1611,13 @@ async function prepareStoreUpdate(definition, snapshot, updater) {
173
1611
  previousState,
174
1612
  draft,
175
1613
  tracked,
176
- replacement: returned
1614
+ replacement: returned,
1615
+ stepKey
177
1616
  });
178
1617
  }
179
1618
  async function finalizeStoreMutation(params) {
180
1619
  const isReplacement = params.replacement !== undefined && params.replacement !== params.draft;
181
- const nextState = await validateStoreState(params.definition, isReplacement ? params.replacement : params.draft);
1620
+ const nextState = await validateStoreState(params.definition, isReplacement ? params.replacement : params.draft, params.stepKey);
182
1621
  const changedPaths = !isReplacement && nextState === params.draft ? params.tracked.writePaths() : diffStorePaths(params.previousState, nextState);
183
1622
  return { nextState, changedPaths };
184
1623
  }
@@ -212,15 +1651,42 @@ function assertSynchronousUpdater(updaterResult) {
212
1651
  throw new Error("Store updaters must be synchronous");
213
1652
  }
214
1653
  }
215
- async function validateStoreState(definition, state) {
1654
+ async function validateStoreState(definition, state, stepKey = "") {
216
1655
  const result = await definition.schema["~standard"].validate(state);
217
1656
  if ("issues" in result && result.issues) {
218
1657
  const messages = result.issues.map((issue) => issue.message).join("; ");
219
- throw new Error(`Invalid store state for "${definition.name}": ${messages}`);
1658
+ throw new StoreValidationError(`Invalid store state for "${definition.name}": ${messages}`);
220
1659
  }
1660
+ assertDurableStoreValue(result.value, stepKey, `Store state for "${definition.name}"`);
221
1661
  return result.value;
222
1662
  }
1663
+ function assertDurableStoreValue(value, stepKey, label) {
1664
+ const encoding = durableValueCodec.encode(value);
1665
+ if (encoding.status === "invalid") {
1666
+ throw new WorkflowValueError("INVALID_STEP_VALUE", stepKey, `${label} is not a durable value: ${encoding.message}`);
1667
+ }
1668
+ }
1669
+ function encodeStoreValue(value) {
1670
+ const encoding = durableValueCodec.encode(value);
1671
+ if (encoding.status === "invalid") {
1672
+ throw new Error(`Store value is not a durable value: ${encoding.message}`);
1673
+ }
1674
+ return encoding.canonical;
1675
+ }
1676
+ function decodeStoreValue(canonical) {
1677
+ const decoding = durableValueCodec.decode(canonical);
1678
+ if (decoding.status === "invalid") {
1679
+ throw new Error(`Invalid store value encoding: ${decoding.message}`);
1680
+ }
1681
+ return decoding.value;
1682
+ }
223
1683
  function cloneStoreState(state) {
1684
+ const encoding = durableValueCodec.encode(state);
1685
+ if (encoding.status === "encoded") {
1686
+ const decoding = durableValueCodec.decode(encoding.canonical);
1687
+ if (decoding.status === "decoded")
1688
+ return decoding.value;
1689
+ }
224
1690
  if (typeof structuredClone === "function") {
225
1691
  try {
226
1692
  return structuredClone(state);
@@ -310,29 +1776,29 @@ function trackStoreUpdater(target) {
310
1776
  if (cached)
311
1777
  return cached;
312
1778
  const proxy = new Proxy(value, {
313
- get(target2, property, receiver) {
1779
+ get(target, property, receiver) {
314
1780
  if (typeof property === "symbol") {
315
1781
  if (property === TRACKING_TARGET)
316
- return target2;
317
- return Reflect.get(target2, property, receiver);
1782
+ return target;
1783
+ return Reflect.get(target, property, receiver);
318
1784
  }
319
- const child = Reflect.get(target2, property);
1785
+ const child = Reflect.get(target, property);
320
1786
  return track(child, [...path, arrayKeyToPathSegment(property)]);
321
1787
  },
322
- set(target2, property, value2) {
1788
+ set(target, property, value) {
323
1789
  record(path, property);
324
- return Reflect.set(target2, property, unwrapTrackedValue(value2));
1790
+ return Reflect.set(target, property, unwrapTrackedValue(value));
325
1791
  },
326
- deleteProperty(target2, property) {
1792
+ deleteProperty(target, property) {
327
1793
  record(path, property);
328
- return Reflect.deleteProperty(target2, property);
1794
+ return Reflect.deleteProperty(target, property);
329
1795
  },
330
- defineProperty(target2, property, descriptor) {
1796
+ defineProperty(target, property, descriptor) {
331
1797
  record(path, property);
332
1798
  if ("value" in descriptor) {
333
1799
  descriptor = { ...descriptor, value: unwrapTrackedValue(descriptor.value) };
334
1800
  }
335
- return Reflect.defineProperty(target2, property, descriptor);
1801
+ return Reflect.defineProperty(target, property, descriptor);
336
1802
  }
337
1803
  });
338
1804
  proxies.set(value, proxy);
@@ -391,9 +1857,12 @@ class StepResult extends StepResponse {
391
1857
  static type = "step-result";
392
1858
  type = "step-result";
393
1859
  result;
394
- constructor(result) {
1860
+ schema;
1861
+ constructor(result, schema) {
395
1862
  super();
396
1863
  this.result = result;
1864
+ if (schema !== undefined)
1865
+ this.schema = schema;
397
1866
  }
398
1867
  }
399
1868
 
@@ -410,10 +1879,10 @@ class WorkflowResult extends StepResponse {
410
1879
  class WorkflowDelay extends StepResponse {
411
1880
  static type = "workflow-delay";
412
1881
  type = "workflow-delay";
413
- resumeIn;
414
- constructor(resumeIn) {
1882
+ wakeAt;
1883
+ constructor(wakeAt) {
415
1884
  super();
416
- this.resumeIn = resumeIn;
1885
+ this.wakeAt = wakeAt;
417
1886
  }
418
1887
  }
419
1888
 
@@ -425,6 +1894,16 @@ class StepStoreWait extends StepResponse {
425
1894
  }
426
1895
  }
427
1896
 
1897
+ class StepEffectWait extends StepResponse {
1898
+ static type = "effect-wait";
1899
+ type = "effect-wait";
1900
+ wakeAt;
1901
+ constructor(wakeAt) {
1902
+ super();
1903
+ this.wakeAt = wakeAt;
1904
+ }
1905
+ }
1906
+
428
1907
  class WorkflowRestart extends StepResponse {
429
1908
  static type = "workflow-restart";
430
1909
  type = "workflow-restart";
@@ -452,54 +1931,66 @@ class StepError extends StepResponse {
452
1931
  class StepDelay extends StepResponse {
453
1932
  static type = "step-delay";
454
1933
  type = "step-delay";
455
- resumeIn;
456
- constructor(resumeIn) {
1934
+ wakeAt;
1935
+ constructor(wakeAt) {
457
1936
  super();
458
- this.resumeIn = resumeIn;
1937
+ this.wakeAt = wakeAt;
459
1938
  }
460
1939
  }
461
1940
  // src/lib/workflow-runner.ts
462
1941
  class WorkflowRunner {
463
- heapClient;
464
- schedulerClient;
465
- storeClient;
1942
+ runtime;
466
1943
  router;
467
- constructor(params) {
468
- this.heapClient = params.heapClient;
469
- this.schedulerClient = params.schedulerClient;
470
- this.storeClient = params.storeClient;
471
- this.router = params.router;
1944
+ logger;
1945
+ constructor(runtime, options) {
1946
+ this.runtime = runtime;
1947
+ this.router = options.router;
1948
+ this.logger = options.logger;
472
1949
  }
473
- run = async (event, logger) => {
1950
+ run = async (event, options) => {
1951
+ const { lease, signal, logger = this.logger } = options;
474
1952
  const workflow = this.router[event.workflowId];
475
1953
  if (!workflow) {
476
- throw new Error(`No workflow registered for "${event.workflowId}"`);
1954
+ return {
1955
+ status: "failed",
1956
+ error: new Error(`No workflow registered for "${event.workflowId}"`)
1957
+ };
477
1958
  }
1959
+ let response;
478
1960
  try {
479
- const response = await this.runWorkflows({
1961
+ response = await this.runWorkflows({
480
1962
  workflow,
481
1963
  logger,
482
- event
1964
+ event,
1965
+ lease,
1966
+ signal
483
1967
  });
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
1968
  } catch (err) {
494
- throw err;
1969
+ if (InfrastructureError.is(err))
1970
+ throw err;
1971
+ return { status: "failed", error: toError(err) };
1972
+ }
1973
+ switch (response.type) {
1974
+ case "workflow-result":
1975
+ return { status: "completed", result: response.result };
1976
+ case "step-delay":
1977
+ case "effect-wait":
1978
+ return { status: "suspended", wakeAt: response.wakeAt };
1979
+ case "store-wait":
1980
+ return { status: "suspended" };
495
1981
  }
496
1982
  };
497
1983
  async runWorkflows(params) {
498
- const { workflow, event, logger } = params;
1984
+ const { workflow, event, lease, signal, logger } = params;
499
1985
  const workflowIterator = workflow({
500
1986
  event,
501
- heapClient: this.heapClient,
502
- storeClient: this.storeClient,
1987
+ lease,
1988
+ signal,
1989
+ heapClient: this.runtime.heap,
1990
+ storeClient: this.runtime.stores,
1991
+ executionClient: this.runtime.executions,
1992
+ effectClient: this.runtime.effects,
1993
+ clock: this.runtime.clock,
503
1994
  logger
504
1995
  });
505
1996
  const iteratorResult = await workflowIterator.next();
@@ -508,117 +1999,107 @@ class WorkflowRunner {
508
1999
  return stageResponse;
509
2000
  }
510
2001
  if (stageResponse instanceof StepDelay) {
511
- return new WorkflowDelay(stageResponse.resumeIn - Date.now());
2002
+ return stageResponse;
512
2003
  }
513
- if (stageResponse instanceof StepStoreWait) {
2004
+ if (stageResponse instanceof StepEffectWait || stageResponse instanceof StepStoreWait) {
514
2005
  return stageResponse;
515
2006
  }
516
2007
  throw new Error("Workflow runner critical error. This is likely because your worklow and worker are referencing different versions of @yieldstar/core.");
517
2008
  }
518
2009
  }
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
- }
2010
+ function toError(value) {
2011
+ return value instanceof Error ? value : new Error(String(value));
594
2012
  }
595
2013
  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,
2014
+ CasStoreClient,
2015
+ DEFAULT_ABORT_GRACE_PERIOD,
2016
+ DEFAULT_DISPATCHER_CONCURRENCY,
2017
+ DEFAULT_LEASE_DURATION,
2018
+ DurableValueError,
2019
+ EffectClientInvariantError,
2020
+ EffectDeliveryError,
2021
+ ExecutionAdmissionError,
2022
+ ExecutionDispatcher,
2023
+ ExecutionEventEncodingError,
622
2024
  FreezableMap,
623
- CasStoreClient
2025
+ InfrastructureError,
2026
+ LeaseLostError,
2027
+ REDELIVERY_DELAY_BASE,
2028
+ REDELIVERY_DELAY_MAX,
2029
+ ReadOnlyMap,
2030
+ Runtime,
2031
+ StepCacheCheck,
2032
+ StepConflictError,
2033
+ StepDelay,
2034
+ StepEffectWait,
2035
+ StepError,
2036
+ StepInvalid,
2037
+ StepKey,
2038
+ StepResponse,
2039
+ StepResult,
2040
+ StepStoreWait,
2041
+ StoreClient,
2042
+ StoreValidationError,
2043
+ WORKFLOW_RESULT_STEP_KEY,
2044
+ WorkerExitedError,
2045
+ WorkflowDelay,
2046
+ WorkflowEffectError,
2047
+ WorkflowRestart,
2048
+ WorkflowResult,
2049
+ WorkflowRunner,
2050
+ WorkflowStartError,
2051
+ WorkflowValueError,
2052
+ acknowledgeExecution,
2053
+ assertSynchronousClaim,
2054
+ assertSynchronousUpdater,
2055
+ claimExecution,
2056
+ cloneStoreState,
2057
+ createEffectState,
2058
+ createExecutionState,
2059
+ createStartedExecution,
2060
+ decodeExecutionEvent,
2061
+ decodeExecutionOutcome,
2062
+ decodeParsedValue,
2063
+ decodeStoreValue,
2064
+ defineStore,
2065
+ diffStorePaths,
2066
+ durableValueCodec,
2067
+ effectNotFound,
2068
+ encodeExecutionEvent,
2069
+ encodeExecutionOutcome,
2070
+ encodeStoreValue,
2071
+ encodeThrownValue,
2072
+ errorWithOriginalStack,
2073
+ executionIdConflict,
2074
+ executionNotFound,
2075
+ executionStatusAt,
2076
+ isExecutionClaimable,
2077
+ isLeaseCurrent,
2078
+ isLeaseLost,
2079
+ isStoreSelectorMatch,
2080
+ isTerminalExecutionStatus,
2081
+ matchEffectBegin,
2082
+ matchStartRequest,
2083
+ nextClaimAt,
2084
+ prepareEffectBegin,
2085
+ prepareEffectSettle,
2086
+ prepareEffectWatch,
2087
+ prepareExecutionAdmission,
2088
+ prepareExecutionStart,
2089
+ redeliveryDelay,
2090
+ registerErrorClass,
2091
+ releaseExecutionLease,
2092
+ renewExecutionLease,
2093
+ requestExecutionWake,
2094
+ settleEffect,
2095
+ storePathsIntersect,
2096
+ toEffectRecord,
2097
+ toExecutionRecord,
2098
+ trackStoreSelector,
2099
+ trackStoreUpdater,
2100
+ unacknowledgedDeliveryCountAt,
2101
+ unwrapTrackedValue,
2102
+ validateEffectRequest,
2103
+ validateStoreState,
2104
+ waitForExecutionOutcome
624
2105
  };