@fluidgrids/workflows 2026.1028.2 → 2026.1029.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 (118) hide show
  1. package/README.md +72 -10
  2. package/dist/{activation-DnhG7SmF.d.ts → activation-BnVQMv9y.d.ts} +1 -1
  3. package/dist/{activation-BBh1p82z.d.cts → activation-CESehfYi.d.cts} +1 -1
  4. package/dist/{chunk-XHXIDVEC.js → chunk-25BKWR2O.js} +1 -1
  5. package/dist/{chunk-UK3WM7YZ.cjs → chunk-2B37XAFQ.cjs} +44 -8
  6. package/dist/{chunk-DJYMDLV5.js → chunk-2DDULZFM.js} +93 -1
  7. package/dist/{chunk-623FVQSN.cjs → chunk-3FIDHONL.cjs} +104 -0
  8. package/dist/chunk-6QPOJZT6.cjs +1180 -0
  9. package/dist/chunk-7ILGCUI6.js +154 -0
  10. package/dist/chunk-A2OHX3UQ.js +1156 -0
  11. package/dist/{chunk-VOKMAAJP.js → chunk-A36CCOWS.js} +188 -19
  12. package/dist/{chunk-FMSW2HDF.cjs → chunk-ELL2RBML.cjs} +0 -14
  13. package/dist/{chunk-JD2VQIDZ.cjs → chunk-EWYADREB.cjs} +2 -2
  14. package/dist/{chunk-HWJZVOG7.js → chunk-FQSJI5NV.js} +1 -1
  15. package/dist/{chunk-ABFZWYHT.js → chunk-FWBRQWRX.js} +156 -48
  16. package/dist/{chunk-PZCVF76Q.cjs → chunk-I47EENR4.cjs} +209 -40
  17. package/dist/chunk-J3PYFGKA.cjs +166 -0
  18. package/dist/{chunk-C5OLWZ42.js → chunk-KXJDRQ6G.js} +1 -13
  19. package/dist/chunk-M272AHEX.js +25 -0
  20. package/dist/{chunk-VRHHBPT6.js → chunk-MJ5TGYVG.js} +333 -65
  21. package/dist/{chunk-SAOBPYYJ.cjs → chunk-NXFBVDOI.cjs} +3 -3
  22. package/dist/{chunk-XKJA7KX3.js → chunk-OIX4O6UR.js} +38 -2
  23. package/dist/{chunk-FEW3JBYA.cjs → chunk-OKX3FYIM.cjs} +3 -3
  24. package/dist/{chunk-M4BBAERY.cjs → chunk-OMY3CKUZ.cjs} +404 -136
  25. package/dist/chunk-PWIUEWC3.js +81 -0
  26. package/dist/chunk-Q4MWQNJK.cjs +28 -0
  27. package/dist/{chunk-P6TA5EZT.cjs → chunk-RUQIMTN5.cjs} +171 -57
  28. package/dist/{chunk-IISBRUWJ.js → chunk-WXXNCHUY.js} +1 -1
  29. package/dist/chunk-YHBVMOMK.cjs +87 -0
  30. package/dist/codegen/index.d.cts +2 -2
  31. package/dist/codegen/index.d.ts +2 -2
  32. package/dist/collect-BTHjQaSp.d.ts +86 -0
  33. package/dist/collect-CSyOg5XC.d.cts +86 -0
  34. package/dist/{file-BOoHZgoQ.d.cts → file-cgL9l9l8.d.cts} +765 -14
  35. package/dist/{file-yuPCFU5b.d.ts → file-olQudcx5.d.ts} +765 -14
  36. package/dist/history/index.cjs +198 -86
  37. package/dist/history/index.d.cts +49 -10
  38. package/dist/history/index.d.ts +49 -10
  39. package/dist/history/index.js +49 -4
  40. package/dist/index.cjs +64 -51
  41. package/dist/index.d.cts +9 -53
  42. package/dist/index.d.ts +9 -53
  43. package/dist/index.js +5 -4
  44. package/dist/ir/index.cjs +24 -3
  45. package/dist/ir/index.d.cts +43 -4
  46. package/dist/ir/index.d.ts +43 -4
  47. package/dist/ir/index.js +24 -3
  48. package/dist/{lower-BzmUz0sq.d.cts → lower-3S6PsqjJ.d.cts} +40 -6
  49. package/dist/{lower-_-iT4HYs.d.ts → lower-DmZpQ8hJ.d.ts} +40 -6
  50. package/dist/{manifest-C8N6HDYl.d.ts → manifest-BJjrQt77.d.ts} +1 -1
  51. package/dist/{manifest-0OLw0i7k.d.cts → manifest-gRI2Q5Fj.d.cts} +1 -1
  52. package/dist/node-evaluator-SOADN23V.js +14 -0
  53. package/dist/node-evaluator-YGDEXK4U.cjs +24 -0
  54. package/dist/quickjs/index.cjs +25 -24
  55. package/dist/quickjs/index.d.cts +6 -6
  56. package/dist/quickjs/index.d.ts +6 -6
  57. package/dist/quickjs/index.js +8 -7
  58. package/dist/roundtrip/index.cjs +245 -43
  59. package/dist/roundtrip/index.d.cts +86 -9
  60. package/dist/roundtrip/index.d.ts +86 -9
  61. package/dist/roundtrip/index.js +172 -8
  62. package/dist/runtime/index.cjs +21 -20
  63. package/dist/runtime/index.d.cts +49 -7
  64. package/dist/runtime/index.d.ts +49 -7
  65. package/dist/runtime/index.js +8 -7
  66. package/dist/testing/index.cjs +514 -74
  67. package/dist/testing/index.d.cts +127 -10
  68. package/dist/testing/index.d.ts +127 -10
  69. package/dist/testing/index.js +480 -42
  70. package/dist/{types-BGoPzgcl.d.ts → types-Bb2wRnJ5.d.cts} +20 -1
  71. package/dist/{types-BGoPzgcl.d.cts → types-Bb2wRnJ5.d.ts} +20 -1
  72. package/dist/{types-DV-HX_Gt.d.ts → types-CQKOhPTa.d.cts} +122 -14
  73. package/dist/{types-CpBBjaGX.d.cts → types-CnIOinoz.d.ts} +122 -14
  74. package/dist/{workflow-Bpr3vnA_.d.ts → workflow-8uRU9rhL.d.ts} +2 -2
  75. package/dist/{workflow-FXUkpIBc.d.cts → workflow-CZOXUlQ5.d.cts} +2 -2
  76. package/dist/{workflow-C2a05WUJ.d.ts → workflow-DWTPHFbt.d.ts} +124 -12
  77. package/dist/{workflow-J-yYixny.d.cts → workflow-O6e64ZcG.d.cts} +124 -12
  78. package/fixtures/histories/auto-upgrade-patched.history.json +276 -0
  79. package/fixtures/histories/benign-failure.history.json +146 -0
  80. package/fixtures/histories/cancel-activity-try-cancel.history.json +150 -0
  81. package/fixtures/histories/child-cancel-wait-requested.history.json +217 -0
  82. package/fixtures/histories/child-options-roundtrip.history.json +192 -0
  83. package/fixtures/histories/continue-as-new-last-result.history.json +86 -0
  84. package/fixtures/histories/continue-as-new-options.history.json +99 -0
  85. package/fixtures/histories/continue-as-new-r1-long-task-queue.history.json +78 -0
  86. package/fixtures/histories/continue-as-new-r1-retry-options.history.json +83 -0
  87. package/fixtures/histories/execution-timeout-chain.history.json +98 -0
  88. package/fixtures/histories/external-cancel-resolved.history.json +123 -0
  89. package/fixtures/histories/external-cancel-unknown-target.history.json +127 -0
  90. package/fixtures/histories/failure-error-types-fails-run.history.json +144 -0
  91. package/fixtures/histories/handler-drain-warn.history.json +222 -0
  92. package/fixtures/histories/heartbeat-details-on-retry.history.json +145 -0
  93. package/fixtures/histories/local-activity-abandoned-at-completion.history.json +73 -0
  94. package/fixtures/histories/local-activity-cancelled-in-scope.history.json +118 -0
  95. package/fixtures/histories/local-activity-completed.history.json +157 -0
  96. package/fixtures/histories/local-activity-failed.history.json +93 -0
  97. package/fixtures/histories/local-activity-retry-in-task.history.json +88 -0
  98. package/fixtures/histories/run-retry-last-failure.history.json +90 -0
  99. package/fixtures/histories/run-timeout.history.json +159 -0
  100. package/fixtures/histories/schedule-to-start-timeout.history.json +132 -0
  101. package/fixtures/histories/start-delay-signal-ends-delay.history.json +155 -0
  102. package/fixtures/histories/typeerror-task-retry-100.history.json +137 -0
  103. package/package.json +1 -1
  104. package/schema/history-file.schema.json +283 -2
  105. package/schema/ir-v2.schema.json +45 -0
  106. package/schema/run-event.schema.json +264 -2
  107. package/schema/workflow-command.schema.json +178 -1
  108. package/schema/workflow-task.schema.json +392 -4
  109. package/dist/assemble-BLqQ8lSu.d.cts +0 -39
  110. package/dist/assemble-DWgaXHd-.d.ts +0 -39
  111. package/dist/chunk-2J6TF7YI.cjs +0 -86
  112. package/dist/chunk-BO6OVILK.js +0 -79
  113. package/dist/chunk-DHTLALBL.cjs +0 -80
  114. package/dist/chunk-I5LR3D2K.js +0 -869
  115. package/dist/chunk-N23SKIG4.js +0 -73
  116. package/dist/chunk-QQ6ULXHI.cjs +0 -885
  117. package/dist/node-evaluator-4MSDKWOG.cjs +0 -22
  118. package/dist/node-evaluator-SX4LEYQC.js +0 -12
@@ -0,0 +1,25 @@
1
+ // src/runtime/stack.ts
2
+ var RUNTIME_FRAME = /fluidgrids-runtime|\/runtime\/(scheduler|index)\.|WorkflowExecution|RuntimeContext/;
3
+ function captureStack() {
4
+ const stack = new Error("stack").stack ?? "";
5
+ return stack.split("\n").slice(1).filter((line) => line.trim() !== "" && !RUNTIME_FRAME.test(line)).join("\n");
6
+ }
7
+ var V8_FRAME = /^\s*at (?:(.*?) \()?(.*?):(\d+):(\d+)\)?$/;
8
+ var QUICKJS_FRAME = /^\s*at (?:(.*?) )?\(?(.*?)(?::(\d+))?(?::(\d+))?\)?$/;
9
+ function parseStack(stack) {
10
+ const locations = [];
11
+ for (const line of stack.split("\n")) {
12
+ if (line.trim() === "") continue;
13
+ const match = V8_FRAME.exec(line) ?? QUICKJS_FRAME.exec(line);
14
+ if (!match) continue;
15
+ const location = {};
16
+ if (match[1]) location.functionName = match[1];
17
+ if (match[2]) location.filePath = match[2];
18
+ if (match[3]) location.line = Number(match[3]);
19
+ if (match[4]) location.column = Number(match[4]);
20
+ locations.push(location);
21
+ }
22
+ return locations;
23
+ }
24
+
25
+ export { captureStack, parseStack };
@@ -1,13 +1,22 @@
1
- import { ServerFailure, failureToError, CancelledFailure, TimeoutFailure, ApplicationError, TerminatedFailure, DeterminismViolation, HistoryLimitExceeded, PhaseCFeatureError, isCancellation, isRunFailure, errorToFailure, optionalMs, ActivityFailure, toMs, signalName, ChildRunFailure } from './chunk-C5OLWZ42.js';
1
+ import { ServerFailure, failureToError, CancelledFailure, TimeoutFailure, ApplicationError, TerminatedFailure, DeterminismViolation, HistoryLimitExceeded, PhaseCFeatureError, isCancellation, isRunFailure, errorToFailure, ActivityFailure, signalName, ChildRunFailure } from './chunk-KXJDRQ6G.js';
2
+ import { captureStack, parseStack } from './chunk-M272AHEX.js';
2
3
  import { createRandom, seedOf, takeShimControl, uuidFrom } from './chunk-4LYSEANA.js';
3
- import { isDurableWorkflow, SDK_VERSION } from './chunk-N23SKIG4.js';
4
- import { utf8Bytes, splitActivations, explicitRetryPolicy, CHILD_DEFAULT_CANCELLATION_TYPE, CHILD_DEFAULT_PARENT_CLOSE_POLICY } from './chunk-ABFZWYHT.js';
5
- import { COMMAND_EVENT_TYPES, DEFAULT_HISTORY_LIMIT, CONTINUE_AS_NEW_SUGGESTED_RATIO, HISTORY_PROTOCOL, HISTORY_LIMIT_EXCEEDED_RATIO, BUILT_IN_QUERIES } from './chunk-DJYMDLV5.js';
6
- import { isActivityNodeTypeAllowed } from './chunk-BO6OVILK.js';
4
+ import { optionalMs, toMs, isDurableWorkflow, runRetryPolicyOf, continueAsNewRetryOf, SDK_VERSION } from './chunk-7ILGCUI6.js';
5
+ import { utf8Bytes, splitActivations, CHILD_DEFAULT_CANCELLATION_TYPE, CHILD_DEFAULT_PARENT_CLOSE_POLICY } from './chunk-FWBRQWRX.js';
6
+ import { COMMAND_EVENT_TYPES, DEFAULT_HISTORY_LIMIT, CONTINUE_AS_NEW_SUGGESTED_RATIO, HISTORY_PROTOCOL, HISTORY_LIMIT_EXCEEDED_RATIO, isActivityNodeTypeAllowed, LOCAL_ACTIVITY_MAX_TIMEOUT_MS, BUILT_IN_QUERIES } from './chunk-2DDULZFM.js';
7
+ import { explicitRetryPolicy, computeRetryDelay } from './chunk-PWIUEWC3.js';
7
8
 
8
9
  // src/runtime/ids.ts
9
10
  var CommandIds = class {
10
- counters = { a: 0, t: 0, c: 0, s: 0, x: 0, m: 0 };
11
+ counters = {
12
+ a: 0,
13
+ t: 0,
14
+ c: 0,
15
+ s: 0,
16
+ x: 0,
17
+ m: 0,
18
+ l: 0
19
+ };
11
20
  /** The next id of a kind: `a1`, `a2`, ... */
12
21
  next(kind) {
13
22
  this.counters[kind] += 1;
@@ -45,7 +54,7 @@ function describeCommand(command) {
45
54
  case "RECORD_MARKER":
46
55
  return `RECORD_MARKER ${command.markerId}`;
47
56
  case "RECORD_LOCAL_ACTIVITY":
48
- return `RECORD_LOCAL_ACTIVITY ${command.localActivityId}`;
57
+ return `RECORD_LOCAL_ACTIVITY ${command.localActivityId}${command.nodeType !== void 0 ? ` (${command.nodeType} ${command.operation ?? ""})` : ""}`;
49
58
  default:
50
59
  return command.type;
51
60
  }
@@ -77,8 +86,10 @@ function mismatch(command, event) {
77
86
  return field(event, "sideEffectId") === command.sideEffectId ? void 0 : differ();
78
87
  case "RECORD_MARKER":
79
88
  return field(event, "markerId") === command.markerId ? void 0 : differ();
80
- case "RECORD_LOCAL_ACTIVITY":
81
- return field(event, "localActivityId") === command.localActivityId ? void 0 : differ();
89
+ case "RECORD_LOCAL_ACTIVITY": {
90
+ const sameOperation = field(event, "nodeType") === void 0 || field(event, "nodeType") === command.nodeType && field(event, "operation") === command.operation;
91
+ return field(event, "localActivityId") === command.localActivityId && sameOperation ? void 0 : differ();
92
+ }
82
93
  default:
83
94
  return void 0;
84
95
  }
@@ -114,30 +125,6 @@ function matchActivation(commands, recorded) {
114
125
  return void 0;
115
126
  }
116
127
 
117
- // src/runtime/stack.ts
118
- var RUNTIME_FRAME = /fluidgrids-runtime|\/runtime\/(scheduler|index)\.|WorkflowExecution|RuntimeContext/;
119
- function captureStack() {
120
- const stack = new Error("stack").stack ?? "";
121
- return stack.split("\n").slice(1).filter((line) => line.trim() !== "" && !RUNTIME_FRAME.test(line)).join("\n");
122
- }
123
- var V8_FRAME = /^\s*at (?:(.*?) \()?(.*?):(\d+):(\d+)\)?$/;
124
- var QUICKJS_FRAME = /^\s*at (?:(.*?) )?\(?(.*?)(?::(\d+))?(?::(\d+))?\)?$/;
125
- function parseStack(stack) {
126
- const locations = [];
127
- for (const line of stack.split("\n")) {
128
- if (line.trim() === "") continue;
129
- const match = V8_FRAME.exec(line) ?? QUICKJS_FRAME.exec(line);
130
- if (!match) continue;
131
- const location = {};
132
- if (match[1]) location.functionName = match[1];
133
- if (match[2]) location.filePath = match[2];
134
- if (match[3]) location.line = Number(match[3]);
135
- if (match[4]) location.column = Number(match[4]);
136
- locations.push(location);
137
- }
138
- return locations;
139
- }
140
-
141
128
  // src/runtime/scheduler.ts
142
129
  var SDK_NAME = "@fluidgrids/workflows";
143
130
  var ContinueAsNewRequest = class {
@@ -251,6 +238,49 @@ function messageOf(error) {
251
238
  function stackTextOf(error) {
252
239
  return error instanceof Error && typeof error.stack === "string" ? error.stack : void 0;
253
240
  }
241
+ function isJsonObject(value) {
242
+ return typeof value === "object" && value !== null && !Array.isArray(value);
243
+ }
244
+ function asFailure(value) {
245
+ return isJsonObject(value) ? value : void 0;
246
+ }
247
+ function infoFromStarted(info, started) {
248
+ if (started?.type !== "WORKFLOW_STARTED") return { ...info };
249
+ const p = started.payload;
250
+ const out = { ...info };
251
+ if (Object.hasOwn(p, "lastCompletionResult")) out.lastResult = p["lastCompletionResult"] ?? null;
252
+ if (Object.hasOwn(p, "lastFailure")) out.lastFailure = asFailure(p["lastFailure"]) ?? null;
253
+ const businessId = typeof p["businessId"] === "string" ? p["businessId"] : void 0;
254
+ const workflowId = typeof p["workflowId"] === "string" ? p["workflowId"] : businessId;
255
+ if (businessId !== void 0) out.businessId = businessId;
256
+ if (workflowId !== void 0) out.workflowId = workflowId;
257
+ if (typeof p["runAttempt"] === "number") {
258
+ out.runAttempt = p["runAttempt"];
259
+ out.attempt = p["runAttempt"];
260
+ }
261
+ if (typeof p["summary"] === "string") out.summary = p["summary"];
262
+ if (typeof p["details"] === "string") out.details = p["details"];
263
+ if (isJsonObject(p["context"])) {
264
+ out.context = { ...p["context"] };
265
+ out.memo = { ...p["context"] };
266
+ }
267
+ const timeouts = {};
268
+ const nested = isJsonObject(p["timeouts"]) ? p["timeouts"] : {};
269
+ for (const key of ["executionTimeout", "runTimeout", "workflowTaskTimeout"]) {
270
+ const value = nested[key] ?? p[key];
271
+ if (typeof value === "number") timeouts[key] = value;
272
+ }
273
+ if (Object.keys(timeouts).length > 0) out.timeouts = timeouts;
274
+ if (p["versioningBehavior"] === "PINNED" || p["versioningBehavior"] === "AUTO_UPGRADE")
275
+ out.versioningBehavior = p["versioningBehavior"];
276
+ if (isJsonObject(p["retry"])) out.retry = p["retry"];
277
+ if (typeof p["priority"] === "number") out.priority = p["priority"];
278
+ if (typeof p["firstRunId"] === "string") out.firstRunId = p["firstRunId"];
279
+ if (typeof p["continuedFromRunId"] === "string") out.continuedFromRunId = p["continuedFromRunId"];
280
+ if (out.workflowId === void 0 && out.businessId !== void 0) out.workflowId = out.businessId;
281
+ if (out.runAttempt === void 0 && out.attempt !== void 0) out.runAttempt = out.attempt;
282
+ return out;
283
+ }
254
284
  var WorkflowExecution = class {
255
285
  request;
256
286
  definition;
@@ -300,7 +330,16 @@ var WorkflowExecution = class {
300
330
  signalHandlers = /* @__PURE__ */ new Map();
301
331
  signalWaiters = /* @__PURE__ */ new Map();
302
332
  defaultSignalHandler;
333
+ defaultSignalPolicy = "WARN_AND_ABANDON";
334
+ signalPolicies = /* @__PURE__ */ new Map();
303
335
  runningHandlers = 0;
336
+ /** Running asynchronous handlers by name, for the drain warning (11 §2.18). */
337
+ running = /* @__PURE__ */ new Map();
338
+ unfinished;
339
+ recordedLocalActivities = /* @__PURE__ */ new Map();
340
+ localActivityResults = /* @__PURE__ */ new Map();
341
+ localActivities = /* @__PURE__ */ new Map();
342
+ memo;
304
343
  conditions = [];
305
344
  currentDetails;
306
345
  context;
@@ -332,17 +371,40 @@ var WorkflowExecution = class {
332
371
  } else if (event.type === "MARKER_RECORDED") {
333
372
  const id = event.payload["markerId"];
334
373
  if (typeof id === "string") this.recordedMarkers.add(id);
374
+ } else if (event.type === "LOCAL_ACTIVITY_COMPLETED" || event.type === "LOCAL_ACTIVITY_FAILED") {
375
+ const id = event.payload["localActivityId"];
376
+ const failure = asFailure(event.payload["failure"]);
377
+ const attempts = event.payload["attempts"];
378
+ if (typeof id === "string")
379
+ this.recordedLocalActivities.set(id, {
380
+ ...event.type === "LOCAL_ACTIVITY_FAILED" && failure ? { failure } : { result: event.payload["result"] ?? null },
381
+ attempts: typeof attempts === "number" ? attempts : 1
382
+ });
335
383
  }
336
384
  }
385
+ if (input.request.kind === "task")
386
+ for (const entry of input.request.localActivityResults ?? [])
387
+ this.localActivityResults.set(entry.localActivityId, {
388
+ ...entry.failure !== void 0 ? { failure: entry.failure } : { result: entry.result ?? null },
389
+ attempts: entry.attempts
390
+ });
337
391
  this.activations = splitActivations(
338
392
  history,
339
393
  input.request.nowMs,
340
394
  input.request.kind !== "query"
341
395
  );
342
396
  const execution = this;
397
+ const snapshot = infoFromStarted(input.request.info, history[0]);
398
+ this.memo = { ...snapshot.memo ?? snapshot.context ?? {} };
343
399
  this.info = {
344
- ...input.request.info,
400
+ ...snapshot,
345
401
  workflowFn: input.request.workflowFn,
402
+ get context() {
403
+ return { ...execution.memo };
404
+ },
405
+ get memo() {
406
+ return { ...execution.memo };
407
+ },
346
408
  get historyLength() {
347
409
  return execution.historyLength;
348
410
  },
@@ -389,12 +451,24 @@ var WorkflowExecution = class {
389
451
  this.checkConditions();
390
452
  this.finishActivation();
391
453
  }
454
+ const requests = this.pendingLocalActivityRequests();
455
+ if (requests.length > 0) {
456
+ const requested = {
457
+ protocol: HISTORY_PROTOCOL,
458
+ outcome: "localActivityRequested",
459
+ commands: [],
460
+ localActivityRequests: requests,
461
+ logs: []
462
+ };
463
+ return requested;
464
+ }
392
465
  const result = {
393
466
  protocol: HISTORY_PROTOCOL,
394
467
  outcome: this.failure ? "failed" : "commands",
395
468
  commands: this.failure ? [] : this.newCommands,
396
469
  logs: this.logs
397
470
  };
471
+ if (!this.failure && this.unfinished !== void 0) result.unfinishedHandlers = this.unfinished;
398
472
  if (this.failure) {
399
473
  result.failure = { cause: this.failure.cause, message: this.failure.message };
400
474
  if (this.failure.stackTrace !== void 0)
@@ -405,6 +479,15 @@ var WorkflowExecution = class {
405
479
  if (this.request.kind === "query") result.queryResults = this.answerQueries();
406
480
  return result;
407
481
  }
482
+ /**
483
+ * The local activities the open activation waits for, when the task is a
484
+ * workflow task that has not failed or closed the run (a run that ends
485
+ * abandons a local activity it did not await).
486
+ */
487
+ pendingLocalActivityRequests() {
488
+ if (this.failure || this.closed || this.request.kind !== "task") return [];
489
+ return [...this.localActivities.values()].filter((entry) => !entry.replayed).map((entry) => entry.request);
490
+ }
408
491
  /** Fail the task (the runtime's own failures and code errors). */
409
492
  failTask(cause, message, stackTrace, seq, errorType) {
410
493
  if (!this.failure) this.failure = new TaskFailure(cause, message, stackTrace, seq, errorType);
@@ -434,11 +517,30 @@ var WorkflowExecution = class {
434
517
  void 0,
435
518
  found.seq
436
519
  );
520
+ else this.checkReplayedLocalActivities();
437
521
  } else {
438
522
  this.newCommands = this.commands;
439
523
  }
440
524
  this.commands = [];
441
525
  }
526
+ /**
527
+ * A local activity issued on a replayed activation without a recorded
528
+ * outcome is fine only when the first execution dropped it in that same
529
+ * activation: its scope was cancelled (the entry is gone) or the run closed.
530
+ * One still waiting means the first execution would have requested and
531
+ * recorded it, so the code diverged from the history.
532
+ */
533
+ checkReplayedLocalActivities() {
534
+ if (this.closed) return;
535
+ for (const entry of this.localActivities.values()) {
536
+ if (!entry.replayed) continue;
537
+ this.failTask(
538
+ "NON_DETERMINISM",
539
+ `Replay of ${entry.description ?? "a local activity"} found no recorded outcome: the code issued a local activity the history does not have`
540
+ );
541
+ return;
542
+ }
543
+ }
442
544
  // ---- inputs ---------------------------------------------------------------
443
545
  deliver(event) {
444
546
  const p = event.payload;
@@ -616,7 +718,7 @@ var WorkflowExecution = class {
616
718
  dispatchSignal(name, payload) {
617
719
  const handler = this.signalHandlers.get(name);
618
720
  if (handler) {
619
- this.runHandler(() => handler(payload));
721
+ this.runHandler(() => handler(payload), name, this.signalPolicies.get(name));
620
722
  return;
621
723
  }
622
724
  const waiters = this.signalWaiters.get(name);
@@ -628,7 +730,7 @@ var WorkflowExecution = class {
628
730
  }
629
731
  if (this.defaultSignalHandler) {
630
732
  const fallback = this.defaultSignalHandler;
631
- this.runHandler(() => fallback(name, payload));
733
+ this.runHandler(() => fallback(name, payload), name, this.defaultSignalPolicy);
632
734
  return;
633
735
  }
634
736
  const buffer = this.signalBuffers.get(name) ?? [];
@@ -644,7 +746,7 @@ var WorkflowExecution = class {
644
746
  if (position >= 0) this.signalOrder.splice(position, 1);
645
747
  return payload;
646
748
  }
647
- runHandler(call) {
749
+ runHandler(call, name, policy = "WARN_AND_ABANDON") {
648
750
  let result;
649
751
  try {
650
752
  result = call();
@@ -654,17 +756,40 @@ var WorkflowExecution = class {
654
756
  }
655
757
  if (result instanceof Promise) {
656
758
  this.runningHandlers += 1;
657
- result.then(
658
- () => {
659
- this.runningHandlers -= 1;
660
- },
661
- (error) => {
662
- this.runningHandlers -= 1;
663
- this.onWorkflowError(error);
664
- }
665
- );
759
+ const entry = this.running.get(name) ?? { count: 0, policy };
760
+ entry.count += 1;
761
+ entry.policy = policy;
762
+ this.running.set(name, entry);
763
+ const done = () => {
764
+ this.runningHandlers -= 1;
765
+ entry.count -= 1;
766
+ if (entry.count === 0) this.running.delete(name);
767
+ };
768
+ result.then(done, (error) => {
769
+ done();
770
+ this.onWorkflowError(error);
771
+ });
666
772
  }
667
773
  }
774
+ /**
775
+ * The drain check of 11 §2.18 when the workflow finishes: handlers still
776
+ * running with policy `WARN_AND_ABANDON` give one warning line and
777
+ * `unfinishedHandlers` in the result. A replay never reports them.
778
+ */
779
+ checkUnfinishedHandlers(command) {
780
+ if (this.replaying || command.type === "CANCEL_WORKFLOW") return;
781
+ const unfinished = [...this.running.entries()].filter(([, entry]) => entry.count > 0 && entry.policy === "WARN_AND_ABANDON").map(([name, entry]) => ({ kind: "signal", name, count: entry.count })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
782
+ if (unfinished.length === 0) return;
783
+ this.unfinished = unfinished;
784
+ const total = unfinished.reduce((sum, entry) => sum + entry.count, 0);
785
+ this.logs.push({
786
+ level: "warn",
787
+ message: `The workflow finished while ${total} signal handler${total === 1 ? " was" : "s were"} still running (${unfinished.map((entry) => `${entry.name} x${entry.count}`).join(
788
+ ", "
789
+ )}); await ctx.allHandlersFinished() before returning, or set unfinishedPolicy: 'ABANDON' on the handler`,
790
+ data: unfinished.map((entry) => ({ kind: entry.kind, name: entry.name, count: entry.count }))
791
+ });
792
+ }
668
793
  // ---- the workflow function ------------------------------------------------
669
794
  startFunction(input) {
670
795
  let promise;
@@ -762,6 +887,7 @@ var WorkflowExecution = class {
762
887
  if (this.closed || this.failure) return;
763
888
  this.commands.push(command);
764
889
  this.closed = true;
890
+ this.checkUnfinishedHandlers(command);
765
891
  }
766
892
  stack() {
767
893
  return this.captureStacks ? captureStack() : void 0;
@@ -839,6 +965,110 @@ var WorkflowExecution = class {
839
965
  this.activities.set(activityId, entry);
840
966
  });
841
967
  }
968
+ /**
969
+ * `ctx.localActivity` (11 §2.12). A replayed activation returns the outcome
970
+ * its history recorded; the open activation takes the outcome the worker
971
+ * passed in `localActivityResults`, or asks for the local activity
972
+ * (`localActivityRequested`) and waits for the same task to run again.
973
+ * Either way the outcome is recorded with `RECORD_LOCAL_ACTIVITY` at the
974
+ * call, so a replay issues the same command at the same position.
975
+ */
976
+ localActivity(scope, operation, options) {
977
+ if (typeof operation === "function")
978
+ throw new PhaseCFeatureError("ctx.localActivity with a function (user local activities)");
979
+ if (typeof operation !== "object" || operation === null || typeof operation.nodeType !== "string")
980
+ throw new TypeError(
981
+ "ctx.localActivity needs a node operation (from nodes.* or the generated clients)"
982
+ );
983
+ const descriptor = operation;
984
+ if (descriptor.role === "trigger" || !isActivityNodeTypeAllowed(descriptor.nodeType))
985
+ throw new ApplicationError(
986
+ `${descriptor.nodeType} cannot run as a durable activity: only store and registry nodes and internal:http, internal:datatable, internal:set and internal:code can`,
987
+ { type: "durable.activityTypeNotAllowed", errorType: "VALIDATION", nonRetryable: true }
988
+ );
989
+ if (typeof options !== "object" || options === null)
990
+ throw new TypeError("ctx.localActivity needs options with args");
991
+ const timeout = options.startToCloseTimeout === void 0 ? LOCAL_ACTIVITY_MAX_TIMEOUT_MS : toMs(options.startToCloseTimeout, "startToCloseTimeout");
992
+ if (timeout < 1 || timeout > LOCAL_ACTIVITY_MAX_TIMEOUT_MS)
993
+ throw new TypeError(
994
+ `A local activity's startToCloseTimeout must be between 1 ms and 30 s (got ${timeout} ms); use ctx.run for longer calls`
995
+ );
996
+ const localActivityId = this.ids.next("l");
997
+ const operationName = options.operation ?? descriptor.operation ?? "execute";
998
+ const nodeVersion = options.nodeVersion ?? descriptor.version;
999
+ const retry = explicitRetryPolicy(options.retry);
1000
+ const args = toJson(options.args, `The args of ${descriptor.nodeType}`);
1001
+ if (scope.consideredCancelled)
1002
+ return Promise.reject(new CancelledFailure("Scope is cancelled"));
1003
+ const label = `local activity ${localActivityId} (${descriptor.nodeType} ${operationName})`;
1004
+ const wrap = (cause) => new ActivityFailure(
1005
+ `Local activity ${localActivityId} (${descriptor.nodeType} ${operationName}) failed: ${cause.message}`,
1006
+ localActivityId,
1007
+ descriptor.nodeType,
1008
+ operationName,
1009
+ cause
1010
+ );
1011
+ const record = (outcome) => {
1012
+ const command = {
1013
+ type: "RECORD_LOCAL_ACTIVITY",
1014
+ localActivityId,
1015
+ attempts: Math.max(1, Math.round(outcome.attempts)),
1016
+ nodeType: descriptor.nodeType,
1017
+ operation: operationName,
1018
+ ...outcome.failure !== void 0 ? { failure: outcome.failure } : { result: outcome.result ?? null }
1019
+ };
1020
+ if (options.summary !== void 0) command.summary = options.summary;
1021
+ if (!this.push(command)) return this.never();
1022
+ if (outcome.failure !== void 0)
1023
+ return Promise.reject(wrap(failureToError(outcome.failure)));
1024
+ return Promise.resolve(outcome.result ?? null);
1025
+ };
1026
+ const replayed = this.replaying;
1027
+ if (replayed) {
1028
+ const recorded = this.recordedLocalActivities.get(localActivityId);
1029
+ if (recorded !== void 0) return record(recorded);
1030
+ }
1031
+ const given = replayed ? void 0 : this.localActivityResults.get(localActivityId);
1032
+ let attempt = 1;
1033
+ let backoffMs = 0;
1034
+ if (given !== void 0) {
1035
+ if (given.failure === void 0) return record(given);
1036
+ const decision = computeRetryDelay(retry, given.attempts, given.failure);
1037
+ if (!decision.retry) return record(given);
1038
+ attempt = given.attempts + 1;
1039
+ backoffMs = decision.delayMs;
1040
+ }
1041
+ const request = {
1042
+ localActivityId,
1043
+ kind: "node",
1044
+ nodeType: descriptor.nodeType,
1045
+ operation: operationName,
1046
+ args,
1047
+ retry,
1048
+ startToCloseTimeout: timeout,
1049
+ attempt,
1050
+ backoffMs
1051
+ };
1052
+ if (nodeVersion !== void 0) request.nodeVersion = nodeVersion;
1053
+ if (options.connection !== void 0) request.connection = options.connection;
1054
+ if (options.summary !== void 0) request.summary = options.summary;
1055
+ return new Promise((resolve, reject) => {
1056
+ const entry = {
1057
+ resolve,
1058
+ reject,
1059
+ description: label,
1060
+ request,
1061
+ replayed
1062
+ };
1063
+ const stack = this.stack();
1064
+ if (stack !== void 0) entry.stack = stack;
1065
+ entry.release = scope.onCancel(() => {
1066
+ if (!this.localActivities.delete(localActivityId)) return;
1067
+ reject(wrap(new CancelledFailure("Local activity cancelled")));
1068
+ });
1069
+ this.localActivities.set(localActivityId, entry);
1070
+ });
1071
+ }
842
1072
  startTimer(scope, durationMs, summary) {
843
1073
  return this.timer(scope, durationMs, summary).promise;
844
1074
  }
@@ -960,13 +1190,18 @@ var WorkflowExecution = class {
960
1190
  });
961
1191
  });
962
1192
  }
963
- onSignal(signal, handler) {
1193
+ onSignal(signal, handler, options = {}) {
1194
+ if (typeof handler !== "function") throw new TypeError("ctx.onSignal needs a handler function");
1195
+ const policy = options.unfinishedPolicy ?? "WARN_AND_ABANDON";
1196
+ if (policy !== "WARN_AND_ABANDON" && policy !== "ABANDON")
1197
+ throw new TypeError("unfinishedPolicy must be WARN_AND_ABANDON or ABANDON");
964
1198
  const name = signalName(signal);
965
1199
  this.signalHandlers.set(name, handler);
1200
+ this.signalPolicies.set(name, policy);
966
1201
  let payload = this.takeBuffered(name);
967
1202
  while (payload !== void 0) {
968
1203
  const value = payload;
969
- this.runHandler(() => handler(value));
1204
+ this.runHandler(() => handler(value), name, policy);
970
1205
  payload = this.takeBuffered(name);
971
1206
  }
972
1207
  }
@@ -976,8 +1211,36 @@ var WorkflowExecution = class {
976
1211
  for (const entry of buffered) {
977
1212
  if (this.signalHandlers.has(entry.name)) continue;
978
1213
  const payload = this.takeBuffered(entry.name);
979
- if (payload !== void 0) this.runHandler(() => handler(entry.name, payload));
1214
+ if (payload !== void 0)
1215
+ this.runHandler(() => handler(entry.name, payload), entry.name, this.defaultSignalPolicy);
1216
+ }
1217
+ }
1218
+ /** The command target of a run handle (11 §2.1, §2.24). */
1219
+ externalTarget(target) {
1220
+ if (typeof target === "string") {
1221
+ if (target === "") throw new TypeError("A run handle needs a run id");
1222
+ return { runId: target };
980
1223
  }
1224
+ if (typeof target === "object" && target !== null) {
1225
+ if ("runId" in target && typeof target.runId === "string" && target.runId !== "")
1226
+ return { runId: target.runId };
1227
+ if ("workflowKey" in target && typeof target.workflowKey === "string" && target.workflowKey !== "" && typeof target.workflowId === "string" && target.workflowId !== "") {
1228
+ const out = { workflowKey: target.workflowKey, workflowId: target.workflowId };
1229
+ if (typeof target.firstRunId === "string" && target.firstRunId !== "")
1230
+ out.firstRunId = target.firstRunId;
1231
+ return out;
1232
+ }
1233
+ }
1234
+ throw new TypeError("A run handle needs a run id or { workflowKey, workflowId }");
1235
+ }
1236
+ runHandle(target) {
1237
+ const resolved = this.externalTarget(target);
1238
+ return {
1239
+ runId: "runId" in resolved ? resolved.runId : "",
1240
+ ..."workflowId" in resolved ? { workflowKey: resolved.workflowKey, workflowId: resolved.workflowId } : {},
1241
+ signal: (signal, payload) => this.signalExternal(resolved, signalName(signal), payload),
1242
+ cancel: () => this.cancelExternal(resolved)
1243
+ };
981
1244
  }
982
1245
  childTarget(target) {
983
1246
  if (typeof target === "string") {
@@ -1004,6 +1267,12 @@ var WorkflowExecution = class {
1004
1267
  if (options.details !== void 0) opts["details"] = options.details;
1005
1268
  if (options.context !== void 0)
1006
1269
  opts["context"] = toJson(options.context, "The child context");
1270
+ if (options.retry !== void 0)
1271
+ opts["retry"] = toJson(runRetryPolicyOf(options.retry), "The child retry policy");
1272
+ if (options.versioningBehavior !== void 0)
1273
+ opts["versioningBehavior"] = options.versioningBehavior;
1274
+ if (options.idReusePolicy !== void 0) opts["idReusePolicy"] = options.idReusePolicy;
1275
+ if (options.idConflictPolicy !== void 0) opts["idConflictPolicy"] = options.idConflictPolicy;
1007
1276
  const durations = [
1008
1277
  ["startDelay", "startDelay"],
1009
1278
  ["executionTimeout", "executionTimeout"],
@@ -1233,17 +1502,16 @@ var WorkflowExecution = class {
1233
1502
  });
1234
1503
  }
1235
1504
  upsertContext(entries) {
1236
- this.push({
1237
- type: "UPSERT_CONTEXT",
1238
- entries: toJson(entries, "The context entries")
1239
- });
1505
+ const json = toJson(entries, "The context entries");
1506
+ if (!isJsonObject(json)) throw new TypeError("ctx.upsertContext takes an object");
1507
+ if (this.push({ type: "UPSERT_CONTEXT", entries: json })) this.memo = { ...this.memo, ...json };
1240
1508
  }
1241
1509
  /** The request a `ctx.continueAsNew` call throws; the run ends with `CONTINUE_AS_NEW`. */
1242
1510
  continueAsNew(input, options) {
1243
1511
  const opts = {};
1244
1512
  if (options.taskQueue !== void 0) opts["taskQueue"] = options.taskQueue;
1245
1513
  if (options.retry !== void 0)
1246
- opts["retry"] = toJson(explicitRetryPolicy(options.retry), "retry");
1514
+ opts["retry"] = toJson(continueAsNewRetryOf(options.retry), "retry");
1247
1515
  if (options.workflowTaskTimeout !== void 0)
1248
1516
  opts["workflowTaskTimeout"] = toMs(options.workflowTaskTimeout, "workflowTaskTimeout");
1249
1517
  if (options.context !== void 0) opts["context"] = toJson(options.context, "context");
@@ -1292,6 +1560,7 @@ var WorkflowExecution = class {
1292
1560
  out.push({ description: entry.description ?? fallback, stack: entry.stack ?? "" });
1293
1561
  };
1294
1562
  for (const entry of this.activities.values()) add(entry, "activity");
1563
+ for (const entry of this.localActivities.values()) add(entry, "local activity");
1295
1564
  for (const entry of this.timers.values()) add(entry, "timer");
1296
1565
  for (const child of this.children.values()) {
1297
1566
  add(child.started, "child start");
@@ -1386,18 +1655,19 @@ ${entry.stack}`).join("\n\n")
1386
1655
  sleep: (duration, options = {}) => this.startTimer(scope(), toMs(duration, "sleep duration"), options.summary),
1387
1656
  condition: (predicate, timeout) => this.condition(scope(), predicate, timeout),
1388
1657
  signalReceived: (signal) => this.signalReceived(scope(), signal),
1389
- onSignal: (signal, handler) => this.onSignal(signal, handler),
1658
+ onSignal: ((signal, optionsOrHandler, maybeHandler) => {
1659
+ if (typeof optionsOrHandler === "function") this.onSignal(signal, optionsOrHandler);
1660
+ else if (maybeHandler !== void 0)
1661
+ this.onSignal(signal, maybeHandler, optionsOrHandler ?? {});
1662
+ else throw new TypeError("ctx.onSignal needs a handler function");
1663
+ }),
1390
1664
  setDefaultSignalHandler: (handler) => this.setDefaultSignalHandler(handler),
1391
1665
  child: (target, options = {}) => this.child(scope(), target, options),
1392
1666
  startChild: (target, options = {}) => this.startChild(scope(), target, options),
1393
- signalRun: (runId, signal, payload) => this.signalExternal({ runId }, signalName(signal), payload),
1667
+ signalRun: (target, signal, payload) => this.signalExternal(this.externalTarget(target), signalName(signal), payload),
1394
1668
  signalParent: (signal, payload) => this.signalExternal({ parent: true }, signalName(signal), payload),
1395
- getRunHandle: (runId) => ({
1396
- runId,
1397
- signal: (signal, payload) => this.signalExternal({ runId }, signalName(signal), payload),
1398
- cancel: () => this.cancelExternal({ runId })
1399
- }),
1400
- cancelRun: (runId) => this.cancelExternal({ runId }),
1669
+ getRunHandle: (target) => this.runHandle(target),
1670
+ cancelRun: (target) => this.cancelExternal(this.externalTarget(target)),
1401
1671
  now: () => this.clock,
1402
1672
  random: () => this.random(),
1403
1673
  uuid: () => uuidFrom(this.random),
@@ -1451,9 +1721,7 @@ ${entry.stack}`).join("\n\n")
1451
1721
  },
1452
1722
  allHandlersFinished: () => this.allHandlersFinished(),
1453
1723
  activity: phaseC("ctx.activity (user activities)"),
1454
- localActivity: phaseC(
1455
- "ctx.localActivity (local activities)"
1456
- ),
1724
+ localActivity: ((operation, options) => this.localActivity(scope(), operation, options)),
1457
1725
  handleQuery: phaseC("ctx.handleQuery (custom queries)"),
1458
1726
  handleUpdate: phaseC("ctx.handleUpdate (updates)"),
1459
1727
  upsertSearchAttributes: phaseC("ctx.upsertSearchAttributes (search attributes)")
@@ -4,7 +4,7 @@ var chunkG4UEPUGO_cjs = require('./chunk-G4UEPUGO.cjs');
4
4
  var chunkGVTZMA5C_cjs = require('./chunk-GVTZMA5C.cjs');
5
5
  var chunkTBDXTV22_cjs = require('./chunk-TBDXTV22.cjs');
6
6
  var chunkSAENRNBJ_cjs = require('./chunk-SAENRNBJ.cjs');
7
- var chunkDHTLALBL_cjs = require('./chunk-DHTLALBL.cjs');
7
+ var chunkJ3PYFGKA_cjs = require('./chunk-J3PYFGKA.cjs');
8
8
 
9
9
  // src/builder/descriptors.ts
10
10
  function op(nodeType, version, operation) {
@@ -334,7 +334,7 @@ var WorkflowBuilder = class {
334
334
  let workflowFn;
335
335
  let definition;
336
336
  if (typeof fn === "string" && fn !== "") workflowFn = fn;
337
- else if (chunkDHTLALBL_cjs.isDurableWorkflow(fn)) {
337
+ else if (chunkJ3PYFGKA_cjs.isDurableWorkflow(fn)) {
338
338
  definition = fn;
339
339
  workflowFn = fn.name;
340
340
  } else throw new chunkGVTZMA5C_cjs.BuilderError("wf.codeWorkflow needs a workflow() function or its export name");
@@ -755,7 +755,7 @@ function assembleRecording(recording, options = {}) {
755
755
  const authoring = options.authoring ?? {
756
756
  mode: "canvas",
757
757
  generator: "sdk-ts",
758
- generatorVersion: chunkDHTLALBL_cjs.SDK_VERSION
758
+ generatorVersion: chunkJ3PYFGKA_cjs.SDK_VERSION
759
759
  };
760
760
  const out = { schemaVersion: 2 };
761
761
  for (const key of ["key", "name", "description", "interface", "code"]) {