@fluidgrids/workflows 2026.1028.2 → 2026.1030.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 (154) hide show
  1. package/README.md +181 -11
  2. package/dist/{activation-BBh1p82z.d.cts → activation-DqUfpHzk.d.cts} +1 -1
  3. package/dist/{activation-DnhG7SmF.d.ts → activation-xpx1oWuo.d.ts} +1 -1
  4. package/dist/{chunk-VRHHBPT6.js → chunk-2D3MYIIF.js} +1212 -128
  5. package/dist/chunk-3U2NU7YC.cjs +124 -0
  6. package/dist/{chunk-NEIUQASE.cjs → chunk-4QKB4ARC.cjs} +3 -3
  7. package/dist/{chunk-GBQ33SIZ.cjs → chunk-54FLQNUM.cjs} +12 -1
  8. package/dist/chunk-5HIVIV53.js +330 -0
  9. package/dist/chunk-5MFVJM76.js +1222 -0
  10. package/dist/{chunk-JD2VQIDZ.cjs → chunk-672KMMBK.cjs} +2 -2
  11. package/dist/{chunk-C5OLWZ42.js → chunk-6A2FGSUS.js} +22 -14
  12. package/dist/{chunk-XKJA7KX3.js → chunk-6OLN6BMB.js} +52 -3
  13. package/dist/{chunk-6FSHT5IL.js → chunk-7EZIANC7.js} +1 -1
  14. package/dist/{chunk-2J6TF7YI.cjs → chunk-A5SXCA62.cjs} +10 -3
  15. package/dist/{chunk-BO6OVILK.js → chunk-AC6ZEXVR.js} +10 -4
  16. package/dist/{chunk-ABFZWYHT.js → chunk-AE5Q6ZAJ.js} +315 -50
  17. package/dist/chunk-C4F5KJTI.js +293 -0
  18. package/dist/{chunk-IISBRUWJ.js → chunk-G5H32GIG.js} +1 -1
  19. package/dist/chunk-GEQDGUGB.cjs +2719 -0
  20. package/dist/{chunk-XHXIDVEC.js → chunk-HCM7ZC7Q.js} +1 -1
  21. package/dist/chunk-HMZUGSMU.cjs +1554 -0
  22. package/dist/{chunk-SAOBPYYJ.cjs → chunk-IQPXHQQQ.cjs} +8 -4
  23. package/dist/{chunk-P6TA5EZT.cjs → chunk-ISOIA7NV.cjs} +335 -58
  24. package/dist/chunk-JSC75O5C.cjs +167 -0
  25. package/dist/chunk-M272AHEX.js +25 -0
  26. package/dist/{chunk-BD7ZPG53.js → chunk-NWQ5WRLE.js} +10 -2
  27. package/dist/chunk-ODZ2IAR2.cjs +1247 -0
  28. package/dist/chunk-Q4MWQNJK.cjs +28 -0
  29. package/dist/{chunk-HWJZVOG7.js → chunk-SR7TR5KX.js} +6 -2
  30. package/dist/{chunk-FEW3JBYA.cjs → chunk-TCTE7DX5.cjs} +3 -3
  31. package/dist/{chunk-623FVQSN.cjs → chunk-TYF56RGB.cjs} +123 -0
  32. package/dist/chunk-VA4OFR72.cjs +324 -0
  33. package/dist/chunk-VU5S5A7V.js +150 -0
  34. package/dist/chunk-YEHR4NNE.js +1522 -0
  35. package/dist/{chunk-FMSW2HDF.cjs → chunk-ZCGKGDCL.cjs} +23 -15
  36. package/dist/codegen/index.d.cts +2 -2
  37. package/dist/codegen/index.d.ts +2 -2
  38. package/dist/collect-6NIEufHd.d.ts +103 -0
  39. package/dist/collect-CcTCKqx6.d.cts +103 -0
  40. package/dist/{file-BOoHZgoQ.d.cts → file-BxYpwjRQ.d.cts} +1007 -21
  41. package/dist/{file-yuPCFU5b.d.ts → file-CFdPQJI9.d.ts} +1007 -21
  42. package/dist/history/index.cjs +375 -86
  43. package/dist/history/index.d.cts +590 -12
  44. package/dist/history/index.d.ts +590 -12
  45. package/dist/history/index.js +50 -4
  46. package/dist/index.cjs +156 -50
  47. package/dist/index.d.cts +38 -58
  48. package/dist/index.d.ts +38 -58
  49. package/dist/index.js +6 -4
  50. package/dist/interceptors-CNcr2dlk.d.ts +159 -0
  51. package/dist/interceptors-CqkNZVfI.d.cts +159 -0
  52. package/dist/ir/index.cjs +362 -38
  53. package/dist/ir/index.d.cts +282 -7
  54. package/dist/ir/index.d.ts +282 -7
  55. package/dist/ir/index.js +329 -23
  56. package/dist/{lower-BzmUz0sq.d.cts → lower-BtRfIW2A.d.cts} +56 -8
  57. package/dist/{lower-_-iT4HYs.d.ts → lower-CpsMFOFX.d.ts} +56 -8
  58. package/dist/{manifest-C8N6HDYl.d.ts → manifest-D-I_twys.d.ts} +1 -1
  59. package/dist/{manifest-0OLw0i7k.d.cts → manifest-DahBQVM8.d.cts} +1 -1
  60. package/dist/node-evaluator-DGQDPSPI.cjs +24 -0
  61. package/dist/node-evaluator-LEEVD7H7.js +14 -0
  62. package/dist/payload-fields-BVRQn2yH.d.cts +122 -0
  63. package/dist/payload-fields-oOBWxDiG.d.ts +122 -0
  64. package/dist/quickjs/index.cjs +28 -27
  65. package/dist/quickjs/index.d.cts +6 -6
  66. package/dist/quickjs/index.d.ts +6 -6
  67. package/dist/quickjs/index.js +10 -9
  68. package/dist/roundtrip/index.cjs +259 -51
  69. package/dist/roundtrip/index.d.cts +100 -9
  70. package/dist/roundtrip/index.d.ts +100 -9
  71. package/dist/roundtrip/index.js +177 -11
  72. package/dist/runtime/index.cjs +21 -20
  73. package/dist/runtime/index.d.cts +184 -15
  74. package/dist/runtime/index.d.ts +184 -15
  75. package/dist/runtime/index.js +8 -7
  76. package/dist/testing/index.cjs +1122 -115
  77. package/dist/testing/index.d.cts +294 -13
  78. package/dist/testing/index.d.ts +294 -13
  79. package/dist/testing/index.js +1086 -84
  80. package/dist/{types-DV-HX_Gt.d.ts → types-Bv2i4cBX.d.cts} +244 -33
  81. package/dist/{types-CpBBjaGX.d.cts → types-DGx-b9eq.d.ts} +244 -33
  82. package/dist/{types-BGoPzgcl.d.ts → types-V7jEEUaL.d.cts} +20 -1
  83. package/dist/{types-BGoPzgcl.d.cts → types-V7jEEUaL.d.ts} +20 -1
  84. package/dist/{workflow-Bpr3vnA_.d.ts → workflow-B18OeF7g.d.ts} +2 -2
  85. package/dist/workflow-BGnv8SS6.d.ts +803 -0
  86. package/dist/{workflow-FXUkpIBc.d.cts → workflow-BJPFv3cp.d.cts} +2 -2
  87. package/dist/workflow-DS32Gtws.d.cts +803 -0
  88. package/fixtures/histories/activity-options-updated.history.json +159 -0
  89. package/fixtures/histories/activity-paused-unpaused.history.json +160 -0
  90. package/fixtures/histories/auto-upgrade-patched.history.json +276 -0
  91. package/fixtures/histories/benign-failure.history.json +146 -0
  92. package/fixtures/histories/cancel-activity-try-cancel.history.json +150 -0
  93. package/fixtures/histories/child-cancel-wait-requested.history.json +217 -0
  94. package/fixtures/histories/child-options-roundtrip.history.json +192 -0
  95. package/fixtures/histories/codec-opaque.history.json +229 -0
  96. package/fixtures/histories/continue-as-new-last-result.history.json +86 -0
  97. package/fixtures/histories/continue-as-new-options.history.json +99 -0
  98. package/fixtures/histories/continue-as-new-r1-long-task-queue.history.json +78 -0
  99. package/fixtures/histories/continue-as-new-r1-retry-options.history.json +83 -0
  100. package/fixtures/histories/dynamic-workflow.history.json +150 -0
  101. package/fixtures/histories/execution-timeout-chain.history.json +98 -0
  102. package/fixtures/histories/external-cancel-resolved.history.json +123 -0
  103. package/fixtures/histories/external-cancel-unknown-target.history.json +127 -0
  104. package/fixtures/histories/failure-error-types-fails-run.history.json +144 -0
  105. package/fixtures/histories/handler-drain-warn.history.json +222 -0
  106. package/fixtures/histories/heartbeat-details-on-retry.history.json +145 -0
  107. package/fixtures/histories/interceptor-headers.history.json +339 -0
  108. package/fixtures/histories/local-activity-abandoned-at-completion.history.json +73 -0
  109. package/fixtures/histories/local-activity-cancelled-in-scope.history.json +118 -0
  110. package/fixtures/histories/local-activity-completed.history.json +157 -0
  111. package/fixtures/histories/local-activity-failed.history.json +93 -0
  112. package/fixtures/histories/local-activity-retry-in-task.history.json +88 -0
  113. package/fixtures/histories/query-custom.history.json +213 -0
  114. package/fixtures/histories/query-default-handler.history.json +120 -0
  115. package/fixtures/histories/reset-forked-reapplied-signals.history.json +239 -0
  116. package/fixtures/histories/reset-reapplied-update-admitted.history.json +228 -0
  117. package/fixtures/histories/run-retry-last-failure.history.json +90 -0
  118. package/fixtures/histories/run-timeout.history.json +159 -0
  119. package/fixtures/histories/schedule-to-start-timeout.history.json +132 -0
  120. package/fixtures/histories/search-attributes-upserted.history.json +177 -0
  121. package/fixtures/histories/start-delay-signal-ends-delay.history.json +155 -0
  122. package/fixtures/histories/typeerror-task-retry-100.history.json +137 -0
  123. package/fixtures/histories/update-accepted-completed.history.json +45 -30
  124. package/fixtures/histories/update-default-handler.history.json +246 -0
  125. package/fixtures/histories/update-handler-awaits-activity.history.json +196 -0
  126. package/fixtures/histories/update-not-handled.history.json +138 -0
  127. package/fixtures/histories/update-rejected-no-event.history.json +190 -0
  128. package/fixtures/histories/update-with-start-admitted.history.json +150 -0
  129. package/fixtures/histories/user-activity-cancel.history.json +149 -0
  130. package/fixtures/histories/user-activity-retry.history.json +143 -0
  131. package/fixtures/histories/user-activity.history.json +149 -0
  132. package/package.json +1 -1
  133. package/schema/bridge-v2.schema.json +1062 -0
  134. package/schema/history-file.schema.json +538 -83
  135. package/schema/ir-v2.schema.json +290 -0
  136. package/schema/payload-fields.json +162 -0
  137. package/schema/run-event.schema.json +503 -82
  138. package/schema/workflow-command.schema.json +372 -69
  139. package/schema/workflow-task.schema.json +924 -171
  140. package/dist/assemble-BLqQ8lSu.d.cts +0 -39
  141. package/dist/assemble-DWgaXHd-.d.ts +0 -39
  142. package/dist/chunk-DHTLALBL.cjs +0 -80
  143. package/dist/chunk-DJYMDLV5.js +0 -227
  144. package/dist/chunk-I5LR3D2K.js +0 -869
  145. package/dist/chunk-M4BBAERY.cjs +0 -1635
  146. package/dist/chunk-N23SKIG4.js +0 -73
  147. package/dist/chunk-PZCVF76Q.cjs +0 -868
  148. package/dist/chunk-QQ6ULXHI.cjs +0 -885
  149. package/dist/chunk-UK3WM7YZ.cjs +0 -75
  150. package/dist/chunk-VOKMAAJP.js +0 -848
  151. package/dist/node-evaluator-4MSDKWOG.cjs +0 -22
  152. package/dist/node-evaluator-SX4LEYQC.js +0 -12
  153. package/dist/workflow-C2a05WUJ.d.ts +0 -444
  154. package/dist/workflow-J-yYixny.d.cts +0 -444
@@ -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 { QueryHandlerSideEffect, ServerFailure, failureToError, CancelledFailure, TimeoutFailure, ApplicationError, TerminatedFailure, errorToFailure, isRunFailure, DeterminismViolation, HistoryLimitExceeded, PhaseCFeatureError, isCancellation, ActivityFailure, signalName, ChildRunFailure } from './chunk-6A2FGSUS.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 { resolveWorkflowInterceptors, compose, optionalMs, isActivityDefinition, checkActivityName, checkTaskQueueName, toMs, isDurableWorkflow, runRetryPolicyOf, continueAsNewRetryOf, interceptorsFactoryOf, SDK_VERSION } from './chunk-C4F5KJTI.js';
5
+ import { utf8Bytes, splitActivations, CHILD_DEFAULT_CANCELLATION_TYPE, CHILD_DEFAULT_PARENT_CLOSE_POLICY, mergeSearchAttributes } from './chunk-AE5Q6ZAJ.js';
6
+ import { isActivityNodeTypeAllowed } from './chunk-AC6ZEXVR.js';
7
+ import { COMMAND_EVENT_TYPES, DEFAULT_HISTORY_LIMIT, CONTINUE_AS_NEW_SUGGESTED_RATIO, HISTORY_PROTOCOL, HISTORY_LIMIT_EXCEEDED_RATIO, explicitRetryPolicy, MAX_ACTIVITY_CONNECTIONS, LOCAL_ACTIVITY_MAX_TIMEOUT_MS, MAX_SINK_CALLS, RUNTIME_CAPABILITIES, computeRetryDelay, FAIRNESS_KEY_MAX_LENGTH, FAIRNESS_WEIGHT_MIN, FAIRNESS_WEIGHT_MAX, BUILT_IN_QUERIES } from './chunk-5HIVIV53.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;
@@ -28,7 +37,7 @@ function field(event, key) {
28
37
  function describeCommand(command) {
29
38
  switch (command.type) {
30
39
  case "SCHEDULE_ACTIVITY":
31
- return `SCHEDULE_ACTIVITY ${command.activityId} (${command.nodeType} ${command.operation})`;
40
+ return command.kind === "user" ? `SCHEDULE_ACTIVITY ${command.activityId} (user ${command.activityName})` : `SCHEDULE_ACTIVITY ${command.activityId} (${command.nodeType} ${command.operation})`;
32
41
  case "REQUEST_CANCEL_ACTIVITY":
33
42
  return `REQUEST_CANCEL_ACTIVITY ${command.activityId}`;
34
43
  case "START_TIMER":
@@ -45,13 +54,17 @@ 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 ?? ""})` : ""}`;
58
+ case "ACCEPT_UPDATE":
59
+ case "REJECT_UPDATE":
60
+ case "COMPLETE_UPDATE":
61
+ return `${command.type} ${command.updateId}`;
49
62
  default:
50
63
  return command.type;
51
64
  }
52
65
  }
53
66
  function describeEvent(event) {
54
- const id = field(event, "activityId") ?? field(event, "timerId") ?? field(event, "childId") ?? field(event, "externalId") ?? field(event, "sideEffectId") ?? field(event, "markerId") ?? field(event, "localActivityId");
67
+ const id = field(event, "activityId") ?? field(event, "timerId") ?? field(event, "childId") ?? field(event, "externalId") ?? field(event, "sideEffectId") ?? field(event, "markerId") ?? field(event, "localActivityId") ?? field(event, "updateId");
55
68
  return `${event.type}${typeof id === "string" ? ` ${id}` : ""} (seq ${event.seq})`;
56
69
  }
57
70
  function mismatch(command, event) {
@@ -60,8 +73,13 @@ function mismatch(command, event) {
60
73
  const differ = () => `the code issued ${describeCommand(command)} where the history has ${describeEvent(event)}`;
61
74
  if (!typeOk) return differ();
62
75
  switch (command.type) {
63
- case "SCHEDULE_ACTIVITY":
64
- return field(event, "activityId") === command.activityId && field(event, "nodeType") === command.nodeType && field(event, "operation") === command.operation ? void 0 : differ();
76
+ case "SCHEDULE_ACTIVITY": {
77
+ if (field(event, "activityId") !== command.activityId) return differ();
78
+ const kind = field(event, "kind") ?? "node";
79
+ if (command.kind === "user")
80
+ return kind === "user" && field(event, "activityName") === command.activityName ? void 0 : differ();
81
+ return kind === "node" && field(event, "nodeType") === command.nodeType && field(event, "operation") === command.operation ? void 0 : differ();
82
+ }
65
83
  case "REQUEST_CANCEL_ACTIVITY":
66
84
  return field(event, "activityId") === command.activityId ? void 0 : differ();
67
85
  case "START_TIMER":
@@ -77,8 +95,13 @@ function mismatch(command, event) {
77
95
  return field(event, "sideEffectId") === command.sideEffectId ? void 0 : differ();
78
96
  case "RECORD_MARKER":
79
97
  return field(event, "markerId") === command.markerId ? void 0 : differ();
80
- case "RECORD_LOCAL_ACTIVITY":
81
- return field(event, "localActivityId") === command.localActivityId ? void 0 : differ();
98
+ case "ACCEPT_UPDATE":
99
+ case "COMPLETE_UPDATE":
100
+ return field(event, "updateId") === command.updateId ? void 0 : differ();
101
+ case "RECORD_LOCAL_ACTIVITY": {
102
+ const sameOperation = field(event, "nodeType") === void 0 || field(event, "nodeType") === command.nodeType && field(event, "operation") === command.operation;
103
+ return field(event, "localActivityId") === command.localActivityId && sameOperation ? void 0 : differ();
104
+ }
82
105
  default:
83
106
  return void 0;
84
107
  }
@@ -90,6 +113,7 @@ function isSkippablePatch(event) {
90
113
  function matchActivation(commands, recorded) {
91
114
  let position = 0;
92
115
  for (const command of commands) {
116
+ if (COMMAND_EVENT_TYPES[command.type] === null) continue;
93
117
  let event = recorded[position];
94
118
  while (event !== void 0 && isSkippablePatch(event) && !(command.type === "RECORD_MARKER" && command.markerId === field(event, "markerId"))) {
95
119
  position += 1;
@@ -114,40 +138,36 @@ function matchActivation(commands, recorded) {
114
138
  return void 0;
115
139
  }
116
140
 
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
141
  // src/runtime/scheduler.ts
142
+ var COMMAND_ID_CALLS = {
143
+ a: "Scheduling an activity",
144
+ t: "Starting a timer",
145
+ c: "Starting a child run",
146
+ s: "ctx.sideEffect()",
147
+ x: "Signalling or cancelling another run",
148
+ m: "ctx.setCurrentDetails()",
149
+ l: "Running a local activity"
150
+ };
151
+ function isBuiltInQuery(name) {
152
+ return BUILT_IN_QUERIES.includes(name);
153
+ }
142
154
  var SDK_NAME = "@fluidgrids/workflows";
143
155
  var ContinueAsNewRequest = class {
144
- constructor(input, options) {
156
+ constructor(input, options, headers = {}) {
145
157
  this.input = input;
146
158
  this.options = options;
159
+ this.headers = headers;
147
160
  }
148
161
  input;
149
162
  options;
163
+ headers;
150
164
  };
165
+ function checkPolicy(policy) {
166
+ const value = policy ?? "WARN_AND_ABANDON";
167
+ if (value !== "WARN_AND_ABANDON" && value !== "ABANDON")
168
+ throw new TypeError("unfinishedPolicy must be WARN_AND_ABANDON or ABANDON");
169
+ return value;
170
+ }
151
171
  var TaskFailure = class {
152
172
  constructor(cause, message, stackTrace, seq, errorType) {
153
173
  this.cause = cause;
@@ -222,6 +242,32 @@ var Scope = class {
222
242
  this.parent?.children.delete(this);
223
243
  }
224
244
  };
245
+ function checkHandlerName(name, what) {
246
+ if (typeof name !== "string" || name === "" || name.startsWith("__"))
247
+ throw new TypeError(`${what} needs a name that does not start with "__"`);
248
+ return name;
249
+ }
250
+ function definitionName(value, what) {
251
+ return checkHandlerName(typeof value === "string" ? value : value?.name, what);
252
+ }
253
+ function isHeaders(value) {
254
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
255
+ }
256
+ function headersOf(value) {
257
+ return isHeaders(value) ? { ...value } : {};
258
+ }
259
+ function checkFairness(options) {
260
+ const key = options.fairnessKey;
261
+ if (key !== void 0 && (typeof key !== "string" || key === "" || key.length > FAIRNESS_KEY_MAX_LENGTH))
262
+ throw new TypeError(
263
+ `fairnessKey must be a string of 1 to ${FAIRNESS_KEY_MAX_LENGTH} characters`
264
+ );
265
+ const weight = options.fairnessWeight;
266
+ if (weight !== void 0 && (typeof weight !== "number" || !Number.isFinite(weight) || weight < FAIRNESS_WEIGHT_MIN || weight > FAIRNESS_WEIGHT_MAX))
267
+ throw new TypeError(
268
+ `fairnessWeight must be a number from ${FAIRNESS_WEIGHT_MIN} to ${FAIRNESS_WEIGHT_MAX}`
269
+ );
270
+ }
225
271
  function toJson(value, what) {
226
272
  if (value === void 0) return null;
227
273
  if (typeof value === "function" || typeof value === "symbol")
@@ -251,6 +297,54 @@ function messageOf(error) {
251
297
  function stackTextOf(error) {
252
298
  return error instanceof Error && typeof error.stack === "string" ? error.stack : void 0;
253
299
  }
300
+ function isJsonObject(value) {
301
+ return typeof value === "object" && value !== null && !Array.isArray(value);
302
+ }
303
+ function asFailure(value) {
304
+ return isJsonObject(value) ? value : void 0;
305
+ }
306
+ function infoFromStarted(info, started) {
307
+ if (started?.type !== "WORKFLOW_STARTED") return { ...info };
308
+ const p = started.payload;
309
+ const out = { ...info };
310
+ if (Object.hasOwn(p, "lastCompletionResult")) out.lastResult = p["lastCompletionResult"] ?? null;
311
+ if (Object.hasOwn(p, "lastFailure")) out.lastFailure = asFailure(p["lastFailure"]) ?? null;
312
+ const businessId = typeof p["businessId"] === "string" ? p["businessId"] : void 0;
313
+ const workflowId = typeof p["workflowId"] === "string" ? p["workflowId"] : businessId;
314
+ if (businessId !== void 0) out.businessId = businessId;
315
+ if (workflowId !== void 0) out.workflowId = workflowId;
316
+ if (typeof p["runAttempt"] === "number") {
317
+ out.runAttempt = p["runAttempt"];
318
+ out.attempt = p["runAttempt"];
319
+ }
320
+ if (typeof p["summary"] === "string") out.summary = p["summary"];
321
+ if (typeof p["details"] === "string") out.details = p["details"];
322
+ if (isJsonObject(p["context"])) {
323
+ out.context = { ...p["context"] };
324
+ out.memo = { ...p["context"] };
325
+ }
326
+ const timeouts = {};
327
+ const nested = isJsonObject(p["timeouts"]) ? p["timeouts"] : {};
328
+ for (const key of ["executionTimeout", "runTimeout", "workflowTaskTimeout"]) {
329
+ const value = nested[key] ?? p[key];
330
+ if (typeof value === "number") timeouts[key] = value;
331
+ }
332
+ if (Object.keys(timeouts).length > 0) out.timeouts = timeouts;
333
+ if (p["versioningBehavior"] === "PINNED" || p["versioningBehavior"] === "AUTO_UPGRADE")
334
+ out.versioningBehavior = p["versioningBehavior"];
335
+ if (isJsonObject(p["retry"])) out.retry = p["retry"];
336
+ if (typeof p["priority"] === "number") out.priority = p["priority"];
337
+ if (typeof p["firstRunId"] === "string") out.firstRunId = p["firstRunId"];
338
+ if (typeof p["continuedFromRunId"] === "string") out.continuedFromRunId = p["continuedFromRunId"];
339
+ if (isHeaders(p["headers"])) out.headers = { ...p["headers"] };
340
+ if (isJsonObject(p["searchAttributes"]))
341
+ out.searchAttributes = mergeSearchAttributes({}, p["searchAttributes"]);
342
+ if (typeof p["fairnessKey"] === "string") out.fairnessKey = p["fairnessKey"];
343
+ if (typeof p["fairnessWeight"] === "number") out.fairnessWeight = p["fairnessWeight"];
344
+ if (out.workflowId === void 0 && out.businessId !== void 0) out.workflowId = out.businessId;
345
+ if (out.runAttempt === void 0 && out.attempt !== void 0) out.runAttempt = out.attempt;
346
+ return out;
347
+ }
254
348
  var WorkflowExecution = class {
255
349
  request;
256
350
  definition;
@@ -261,7 +355,7 @@ var WorkflowExecution = class {
261
355
  activationOpen = false;
262
356
  replaying = false;
263
357
  clock = 0;
264
- random;
358
+ randomSource;
265
359
  ids = new CommandIds();
266
360
  commands = [];
267
361
  newCommands = [];
@@ -300,7 +394,34 @@ var WorkflowExecution = class {
300
394
  signalHandlers = /* @__PURE__ */ new Map();
301
395
  signalWaiters = /* @__PURE__ */ new Map();
302
396
  defaultSignalHandler;
397
+ defaultSignalPolicy = "WARN_AND_ABANDON";
398
+ signalPolicies = /* @__PURE__ */ new Map();
303
399
  runningHandlers = 0;
400
+ /** Running asynchronous handlers by name, for the drain warning (11 §2.18). */
401
+ running = /* @__PURE__ */ new Map();
402
+ unfinished;
403
+ /** Updates delivered so far, by id (an update is delivered at most once per task). */
404
+ deliveredUpdates = /* @__PURE__ */ new Set();
405
+ /** Updates delivered in the current activation without a handler yet. */
406
+ bufferedUpdates = [];
407
+ updateHandlers = /* @__PURE__ */ new Map();
408
+ defaultUpdateHandler;
409
+ queryHandlers = /* @__PURE__ */ new Map();
410
+ defaultQueryHandler;
411
+ /** Set while a query handler or an update validator runs: commands and sinks throw. */
412
+ readOnly;
413
+ /** The first state-changing call a read-only handler made, even when it caught the error. */
414
+ readOnlyViolation;
415
+ searchAttributes;
416
+ startHeaders;
417
+ inbound;
418
+ outbound;
419
+ sinkCalls = [];
420
+ sinkCallsDropped = false;
421
+ recordedLocalActivities = /* @__PURE__ */ new Map();
422
+ localActivityResults = /* @__PURE__ */ new Map();
423
+ localActivities = /* @__PURE__ */ new Map();
424
+ memo;
304
425
  conditions = [];
305
426
  currentDetails;
306
427
  context;
@@ -323,7 +444,7 @@ var WorkflowExecution = class {
323
444
  this.limitEvents = limit.events;
324
445
  this.limitBytes = limit.bytes;
325
446
  this.captureStacks = input.request.kind === "query";
326
- this.random = createRandom(seedOf(randomnessSeedOf(input.request)));
447
+ this.randomSource = createRandom(seedOf(randomnessSeedOf(input.request)));
327
448
  for (const event of history) {
328
449
  if (event.type === "SIDE_EFFECT_RECORDED") {
329
450
  const id = event.payload["sideEffectId"];
@@ -332,17 +453,67 @@ var WorkflowExecution = class {
332
453
  } else if (event.type === "MARKER_RECORDED") {
333
454
  const id = event.payload["markerId"];
334
455
  if (typeof id === "string") this.recordedMarkers.add(id);
456
+ } else if (event.type === "LOCAL_ACTIVITY_COMPLETED" || event.type === "LOCAL_ACTIVITY_FAILED") {
457
+ const id = event.payload["localActivityId"];
458
+ const failure = asFailure(event.payload["failure"]);
459
+ const attempts = event.payload["attempts"];
460
+ if (typeof id === "string")
461
+ this.recordedLocalActivities.set(id, {
462
+ ...event.type === "LOCAL_ACTIVITY_FAILED" && failure ? { failure } : { result: event.payload["result"] ?? null },
463
+ attempts: typeof attempts === "number" ? attempts : 1
464
+ });
335
465
  }
336
466
  }
467
+ if (input.request.kind === "task")
468
+ for (const entry of input.request.localActivityResults ?? [])
469
+ this.localActivityResults.set(entry.localActivityId, {
470
+ ...entry.failure !== void 0 ? { failure: entry.failure } : { result: entry.result ?? null },
471
+ attempts: entry.attempts
472
+ });
337
473
  this.activations = splitActivations(
338
474
  history,
339
475
  input.request.nowMs,
340
476
  input.request.kind !== "query"
341
477
  );
342
478
  const execution = this;
479
+ const snapshot = infoFromStarted(input.request.info, history[0]);
480
+ this.memo = { ...snapshot.memo ?? snapshot.context ?? {} };
481
+ this.searchAttributes = { ...snapshot.searchAttributes ?? {} };
482
+ this.startHeaders = { ...snapshot.headers ?? {} };
483
+ let inbound = [];
484
+ let outbound = [];
485
+ const factories = input.interceptors ?? [];
486
+ if (factories.length > 0 && input.request.kind === "task" && !(input.request.features ?? []).includes("interceptors"))
487
+ this.failTask(
488
+ "UNSUPPORTED_COMMAND",
489
+ "The bundle exports interceptors, which this workspace does not enable (needs the fluidgrids.run-control flag)"
490
+ );
491
+ else
492
+ try {
493
+ const resolved = resolveWorkflowInterceptors(factories);
494
+ inbound = resolved.inbound;
495
+ outbound = resolved.outbound;
496
+ } catch (error) {
497
+ this.failTask(
498
+ "WORKFLOW_CODE_ERROR",
499
+ `The interceptors factory failed: ${messageOf(error)}`,
500
+ stackTextOf(error)
501
+ );
502
+ }
503
+ this.inbound = inbound;
504
+ this.outbound = outbound;
343
505
  this.info = {
344
- ...input.request.info,
506
+ ...snapshot,
345
507
  workflowFn: input.request.workflowFn,
508
+ get context() {
509
+ return { ...execution.memo };
510
+ },
511
+ get memo() {
512
+ return { ...execution.memo };
513
+ },
514
+ get searchAttributes() {
515
+ return { ...execution.searchAttributes };
516
+ },
346
517
  get historyLength() {
347
518
  return execution.historyLength;
348
519
  },
@@ -365,9 +536,33 @@ var WorkflowExecution = class {
365
536
  now() {
366
537
  return this.clock;
367
538
  }
368
- /** The next deterministic random number. */
539
+ /** The next deterministic random number (`Math.random()` and `crypto.getRandomValues()`). */
369
540
  nextRandom() {
370
- return this.random();
541
+ this.assertWritable("Math.random() or crypto.getRandomValues()");
542
+ return this.randomSource();
543
+ }
544
+ /** `ctx.random()`: advances the run's random source, so read-only handlers may not call it. */
545
+ random() {
546
+ this.assertWritable("ctx.random()");
547
+ return this.randomSource();
548
+ }
549
+ /**
550
+ * Throw `QueryHandlerSideEffect` when a query handler or an update validator
551
+ * calls something that changes the run's deterministic state (the random
552
+ * source, the command id counters, patches, details). Validators are not run
553
+ * again on replay, and a worker that keeps a run's state between tasks keeps
554
+ * whatever a query handler changed, so either would make the next replay
555
+ * diverge. The violation is remembered, so catching the error does not hide it.
556
+ */
557
+ assertWritable(what) {
558
+ if (this.readOnly === void 0) return;
559
+ this.readOnlyViolation ??= what;
560
+ throw new QueryHandlerSideEffect(`${what} (in ${this.readOnly})`);
561
+ }
562
+ /** The next command id of a kind; refused in read-only handlers (it advances a counter). */
563
+ nextId(kind) {
564
+ this.assertWritable(COMMAND_ID_CALLS[kind]);
565
+ return this.ids.next(kind);
371
566
  }
372
567
  /** Begin or finish an activation; `done` when there is nothing left to do. */
373
568
  step() {
@@ -389,12 +584,26 @@ var WorkflowExecution = class {
389
584
  this.checkConditions();
390
585
  this.finishActivation();
391
586
  }
587
+ const requests = this.pendingLocalActivityRequests();
588
+ if (requests.length > 0) {
589
+ const requested = {
590
+ protocol: HISTORY_PROTOCOL,
591
+ outcome: "localActivityRequested",
592
+ commands: [],
593
+ localActivityRequests: requests,
594
+ logs: []
595
+ };
596
+ return requested;
597
+ }
392
598
  const result = {
393
599
  protocol: HISTORY_PROTOCOL,
394
600
  outcome: this.failure ? "failed" : "commands",
395
601
  commands: this.failure ? [] : this.newCommands,
396
602
  logs: this.logs
397
603
  };
604
+ if (!this.failure && this.unfinished !== void 0) result.unfinishedHandlers = this.unfinished;
605
+ if (!this.failure && this.request.kind === "task" && this.sinkCalls.length > 0)
606
+ result.sinkCalls = this.sinkCalls;
398
607
  if (this.failure) {
399
608
  result.failure = { cause: this.failure.cause, message: this.failure.message };
400
609
  if (this.failure.stackTrace !== void 0)
@@ -402,9 +611,19 @@ var WorkflowExecution = class {
402
611
  if (this.failure.seq !== void 0) result.failure.seq = this.failure.seq;
403
612
  if (this.failure.errorType !== void 0) result.failure.errorType = this.failure.errorType;
404
613
  }
405
- if (this.request.kind === "query") result.queryResults = this.answerQueries();
614
+ if (this.request.kind === "query" || (this.request.queries ?? []).length > 0)
615
+ result.queryResults = this.answerQueries();
406
616
  return result;
407
617
  }
618
+ /**
619
+ * The local activities the open activation waits for, when the task is a
620
+ * workflow task that has not failed or closed the run (a run that ends
621
+ * abandons a local activity it did not await).
622
+ */
623
+ pendingLocalActivityRequests() {
624
+ if (this.failure || this.closed || this.request.kind !== "task") return [];
625
+ return [...this.localActivities.values()].filter((entry) => !entry.replayed).map((entry) => entry.request);
626
+ }
408
627
  /** Fail the task (the runtime's own failures and code errors). */
409
628
  failTask(cause, message, stackTrace, seq, errorType) {
410
629
  if (!this.failure) this.failure = new TaskFailure(cause, message, stackTrace, seq, errorType);
@@ -420,11 +639,54 @@ var WorkflowExecution = class {
420
639
  if (this.failure) return;
421
640
  this.deliver(event);
422
641
  }
642
+ for (const update of this.updatesOf(activation)) {
643
+ if (this.failure) return;
644
+ this.deliverUpdate(update);
645
+ }
646
+ }
647
+ /**
648
+ * The updates an activation delivers after its inputs (C-ABI-H): a replayed
649
+ * activation re-delivers the updates its task accepted (validators
650
+ * skipped); the open activation of a workflow task delivers the lease's
651
+ * pending updates. Updates history admitted (`UPDATE_ADMITTED`) arrive as
652
+ * inputs instead.
653
+ */
654
+ updatesOf(activation) {
655
+ const out = [];
656
+ if (activation.recorded) {
657
+ for (const event of activation.recorded) {
658
+ if (event.type !== "UPDATE_ACCEPTED") continue;
659
+ const p = event.payload;
660
+ const updateId = typeof p["updateId"] === "string" ? p["updateId"] : "";
661
+ if (this.deliveredUpdates.has(updateId)) continue;
662
+ out.push({
663
+ updateId,
664
+ name: typeof p["name"] === "string" ? p["name"] : "",
665
+ args: p["args"] ?? null,
666
+ headers: headersOf(p["headers"]),
667
+ validate: false
668
+ });
669
+ }
670
+ return out;
671
+ }
672
+ if (this.request.kind === "query") return out;
673
+ for (const update of this.request.updates ?? []) {
674
+ if (update.admitted === true || this.deliveredUpdates.has(update.updateId)) continue;
675
+ out.push({
676
+ updateId: update.updateId,
677
+ name: update.name,
678
+ args: update.args ?? null,
679
+ headers: headersOf(update.headers),
680
+ validate: true
681
+ });
682
+ }
683
+ return out;
423
684
  }
424
685
  finishActivation() {
425
686
  this.activationOpen = false;
426
687
  const activation = this.activations[this.index];
427
688
  if (!activation) return;
689
+ this.rejectUnhandledUpdates();
428
690
  if (activation.recorded) {
429
691
  const found = matchActivation(this.commands, activation.recorded);
430
692
  if (found)
@@ -434,11 +696,30 @@ var WorkflowExecution = class {
434
696
  void 0,
435
697
  found.seq
436
698
  );
699
+ else this.checkReplayedLocalActivities();
437
700
  } else {
438
701
  this.newCommands = this.commands;
439
702
  }
440
703
  this.commands = [];
441
704
  }
705
+ /**
706
+ * A local activity issued on a replayed activation without a recorded
707
+ * outcome is fine only when the first execution dropped it in that same
708
+ * activation: its scope was cancelled (the entry is gone) or the run closed.
709
+ * One still waiting means the first execution would have requested and
710
+ * recorded it, so the code diverged from the history.
711
+ */
712
+ checkReplayedLocalActivities() {
713
+ if (this.closed) return;
714
+ for (const entry of this.localActivities.values()) {
715
+ if (!entry.replayed) continue;
716
+ this.failTask(
717
+ "NON_DETERMINISM",
718
+ `Replay of ${entry.description ?? "a local activity"} found no recorded outcome: the code issued a local activity the history does not have`
719
+ );
720
+ return;
721
+ }
722
+ }
442
723
  // ---- inputs ---------------------------------------------------------------
443
724
  deliver(event) {
444
725
  const p = event.payload;
@@ -465,8 +746,20 @@ var WorkflowExecution = class {
465
746
  return;
466
747
  }
467
748
  case "SIGNAL_RECEIVED":
468
- this.dispatchSignal(id("name"), p["payload"] ?? null);
749
+ this.dispatchSignal(id("name"), p["payload"] ?? null, headersOf(p["headers"]));
469
750
  return;
751
+ case "UPDATE_ADMITTED": {
752
+ const updateId = id("updateId");
753
+ if (this.deliveredUpdates.has(updateId)) return;
754
+ this.deliverUpdate({
755
+ updateId,
756
+ name: id("name"),
757
+ args: p["args"] ?? null,
758
+ headers: headersOf(p["headers"]),
759
+ validate: true
760
+ });
761
+ return;
762
+ }
470
763
  case "CHILD_RUN_STARTED":
471
764
  case "CHILD_RUN_COMPLETED":
472
765
  case "CHILD_RUN_FAILED":
@@ -516,12 +809,14 @@ var WorkflowExecution = class {
516
809
  pending.release?.();
517
810
  const p = event.payload;
518
811
  const failure = typeof p["failure"] === "object" && p["failure"] !== null && !Array.isArray(p["failure"]) ? p["failure"] : void 0;
812
+ const label = pending.activityName !== void 0 ? `user ${pending.activityName}` : `${pending.nodeType} ${pending.operation}`;
519
813
  const wrap = (cause) => new ActivityFailure(
520
- `Activity ${activityId} (${pending.nodeType} ${pending.operation}) failed: ${cause.message}`,
814
+ `Activity ${activityId} (${label}) failed: ${cause.message}`,
521
815
  activityId,
522
816
  pending.nodeType,
523
817
  pending.operation,
524
- cause
818
+ cause,
819
+ pending.activityName
525
820
  );
526
821
  switch (event.type) {
527
822
  case "ACTIVITY_COMPLETED":
@@ -613,10 +908,24 @@ var WorkflowExecution = class {
613
908
  else result.reject(outcome.error);
614
909
  }
615
910
  }
616
- dispatchSignal(name, payload) {
911
+ /** Run a signal handler through the inbound `handleSignal` interceptors. */
912
+ signalCall(name, payload, headers, handler) {
913
+ if (this.inbound.length === 0) return () => handler(payload);
914
+ const chain = compose(
915
+ this.inbound,
916
+ "handleSignal",
917
+ (input) => handler(input.payload)
918
+ );
919
+ return () => chain({ name, payload, headers });
920
+ }
921
+ dispatchSignal(name, payload, headers = {}) {
617
922
  const handler = this.signalHandlers.get(name);
618
923
  if (handler) {
619
- this.runHandler(() => handler(payload));
924
+ this.runHandler(
925
+ this.signalCall(name, payload, headers, handler),
926
+ name,
927
+ this.signalPolicies.get(name)
928
+ );
620
929
  return;
621
930
  }
622
931
  const waiters = this.signalWaiters.get(name);
@@ -628,7 +937,11 @@ var WorkflowExecution = class {
628
937
  }
629
938
  if (this.defaultSignalHandler) {
630
939
  const fallback = this.defaultSignalHandler;
631
- this.runHandler(() => fallback(name, payload));
940
+ this.runHandler(
941
+ this.signalCall(name, payload, headers, (value) => fallback(name, value)),
942
+ name,
943
+ this.defaultSignalPolicy
944
+ );
632
945
  return;
633
946
  }
634
947
  const buffer = this.signalBuffers.get(name) ?? [];
@@ -644,7 +957,7 @@ var WorkflowExecution = class {
644
957
  if (position >= 0) this.signalOrder.splice(position, 1);
645
958
  return payload;
646
959
  }
647
- runHandler(call) {
960
+ runHandler(call, name, policy = "WARN_AND_ABANDON") {
648
961
  let result;
649
962
  try {
650
963
  result = call();
@@ -653,23 +966,265 @@ var WorkflowExecution = class {
653
966
  return;
654
967
  }
655
968
  if (result instanceof Promise) {
656
- this.runningHandlers += 1;
969
+ const done = this.trackHandler("signal", name, policy);
970
+ result.then(done, (error) => {
971
+ done();
972
+ this.onWorkflowError(error);
973
+ });
974
+ }
975
+ }
976
+ /** Count a running asynchronous handler (drain check, `allHandlersFinished`); returns its end. */
977
+ trackHandler(kind, name, policy) {
978
+ this.runningHandlers += 1;
979
+ const key = `${kind}:${name}`;
980
+ const entry = this.running.get(key) ?? { kind, name, count: 0, policy };
981
+ entry.count += 1;
982
+ entry.policy = policy;
983
+ this.running.set(key, entry);
984
+ return () => {
985
+ this.runningHandlers -= 1;
986
+ entry.count -= 1;
987
+ if (entry.count === 0) this.running.delete(key);
988
+ };
989
+ }
990
+ // ---- updates (Phase C) ----------------------------------------------------
991
+ deliverUpdate(update) {
992
+ this.deliveredUpdates.add(update.updateId);
993
+ const entry = this.updateHandlerFor(update.name);
994
+ if (!entry) {
995
+ this.bufferedUpdates.push(update);
996
+ return;
997
+ }
998
+ this.runUpdate(update, entry);
999
+ }
1000
+ updateHandlerFor(name) {
1001
+ const own = this.updateHandlers.get(name);
1002
+ if (own) return own;
1003
+ const fallback = this.defaultUpdateHandler;
1004
+ if (!fallback) return void 0;
1005
+ const validate = fallback.validate;
1006
+ return {
1007
+ handler: (args) => fallback.handler(name, args),
1008
+ ...validate !== void 0 ? { validate: (args) => validate(name, args) } : {},
1009
+ policy: fallback.policy
1010
+ };
1011
+ }
1012
+ /**
1013
+ * Run `fn` with commands, sinks and every other change to the run's
1014
+ * deterministic state refused (query handlers, update validators). A
1015
+ * refused call fails the whole call with `QueryHandlerSideEffect`, even
1016
+ * when the code caught the error it threw.
1017
+ */
1018
+ readOnlyCall(what, fn) {
1019
+ const previous = this.readOnly;
1020
+ const previousViolation = this.readOnlyViolation;
1021
+ this.readOnly = what;
1022
+ this.readOnlyViolation = void 0;
1023
+ try {
1024
+ let value;
1025
+ try {
1026
+ value = fn();
1027
+ } catch (error) {
1028
+ const violation2 = this.readOnlyViolation;
1029
+ if (violation2 !== void 0 && !(error instanceof QueryHandlerSideEffect))
1030
+ throw new QueryHandlerSideEffect(`${violation2} (in ${what})`);
1031
+ throw error;
1032
+ }
1033
+ const violation = this.readOnlyViolation;
1034
+ if (violation !== void 0) throw new QueryHandlerSideEffect(`${violation} (in ${what})`);
1035
+ return value;
1036
+ } finally {
1037
+ this.readOnly = previous;
1038
+ this.readOnlyViolation = previousViolation;
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Validate, accept and run one update. A validator error rejects it
1043
+ * (`REJECT_UPDATE`, no history); otherwise `ACCEPT_UPDATE` is issued and
1044
+ * the handler's result or failure completes it (`COMPLETE_UPDATE`). A
1045
+ * handler that throws something other than a failure class fails the task,
1046
+ * as a signal handler does.
1047
+ */
1048
+ runUpdate(update, entry) {
1049
+ if (this.closed || this.failure) return;
1050
+ const input = {
1051
+ updateId: update.updateId,
1052
+ name: update.name,
1053
+ args: update.args,
1054
+ headers: update.headers
1055
+ };
1056
+ if (update.validate && (entry.validate !== void 0 || this.inbound.length > 0)) {
1057
+ const validate = compose(
1058
+ this.inbound,
1059
+ "validateUpdate",
1060
+ (checked) => entry.validate?.(checked.args)
1061
+ );
1062
+ try {
1063
+ this.readOnlyCall(`The validator of update ${update.name}`, () => validate(input));
1064
+ } catch (error) {
1065
+ if (error instanceof QueryHandlerSideEffect) {
1066
+ this.failTask("WORKFLOW_CODE_ERROR", messageOf(error), stackTextOf(error));
1067
+ return;
1068
+ }
1069
+ const failure = errorToFailure(error);
1070
+ delete failure.stackTrace;
1071
+ this.commands.push({ type: "REJECT_UPDATE", updateId: update.updateId, failure });
1072
+ return;
1073
+ }
1074
+ }
1075
+ if (!this.push({ type: "ACCEPT_UPDATE", updateId: update.updateId })) return;
1076
+ const handle = compose(
1077
+ this.inbound,
1078
+ "handleUpdate",
1079
+ (accepted) => entry.handler(accepted.args)
1080
+ );
1081
+ let result;
1082
+ try {
1083
+ result = handle(input);
1084
+ } catch (error) {
1085
+ this.completeUpdate(update, { ok: false, error });
1086
+ return;
1087
+ }
1088
+ if (result instanceof Promise) {
1089
+ const done = this.trackHandler("update", update.name, entry.policy);
657
1090
  result.then(
658
- () => {
659
- this.runningHandlers -= 1;
1091
+ (value) => {
1092
+ done();
1093
+ this.completeUpdate(update, { ok: true, value });
660
1094
  },
661
1095
  (error) => {
662
- this.runningHandlers -= 1;
663
- this.onWorkflowError(error);
1096
+ done();
1097
+ this.completeUpdate(update, { ok: false, error });
664
1098
  }
665
1099
  );
1100
+ return;
1101
+ }
1102
+ this.completeUpdate(update, { ok: true, value: result });
1103
+ }
1104
+ completeUpdate(update, outcome) {
1105
+ if (this.closed || this.failure) return;
1106
+ if (outcome.ok) {
1107
+ let value;
1108
+ try {
1109
+ value = toJson(outcome.value, `The result of update ${update.name}`);
1110
+ } catch (error2) {
1111
+ this.onWorkflowError(error2);
1112
+ return;
1113
+ }
1114
+ this.push({ type: "COMPLETE_UPDATE", updateId: update.updateId, result: value });
1115
+ return;
666
1116
  }
1117
+ const error = outcome.error;
1118
+ if (error instanceof ContinueAsNewRequest || !(isRunFailure(error) || this.isFailureType(error))) {
1119
+ this.onWorkflowError(error);
1120
+ return;
1121
+ }
1122
+ const failure = errorToFailure(error);
1123
+ delete failure.stackTrace;
1124
+ this.push({ type: "COMPLETE_UPDATE", updateId: update.updateId, failure });
1125
+ }
1126
+ /** Reject the updates of this activation that no handler took (`UpdateNotHandled`). */
1127
+ rejectUnhandledUpdates() {
1128
+ const pending = this.bufferedUpdates;
1129
+ this.bufferedUpdates = [];
1130
+ if (this.closed || this.failure) return;
1131
+ for (const update of pending)
1132
+ this.commands.push({
1133
+ type: "REJECT_UPDATE",
1134
+ updateId: update.updateId,
1135
+ failure: {
1136
+ errorType: "VALIDATION",
1137
+ type: "UpdateNotHandled",
1138
+ message: `The workflow has no handler for update "${update.name}"`,
1139
+ source: "sdk-ts"
1140
+ }
1141
+ });
1142
+ }
1143
+ handleUpdate(update, handler, options = {}) {
1144
+ const name = definitionName(update, "ctx.handleUpdate");
1145
+ if (typeof handler !== "function")
1146
+ throw new TypeError("ctx.handleUpdate needs a handler function");
1147
+ if (options.validate !== void 0 && typeof options.validate !== "function")
1148
+ throw new TypeError("validate must be a function");
1149
+ const policy = checkPolicy(options.unfinishedPolicy);
1150
+ this.updateHandlers.set(name, {
1151
+ handler,
1152
+ ...options.validate !== void 0 ? { validate: options.validate } : {},
1153
+ policy
1154
+ });
1155
+ this.dispatchBufferedUpdates();
1156
+ }
1157
+ setDefaultUpdateHandler(first, second) {
1158
+ const handler = typeof first === "function" ? first : second;
1159
+ const options = (typeof first === "function" ? second : first) ?? {};
1160
+ if (typeof handler !== "function" || typeof options !== "object")
1161
+ throw new TypeError("ctx.setDefaultUpdateHandler needs a handler function");
1162
+ if (options.validate !== void 0 && typeof options.validate !== "function")
1163
+ throw new TypeError("validate must be a function");
1164
+ this.defaultUpdateHandler = {
1165
+ handler,
1166
+ ...options.validate !== void 0 ? { validate: options.validate } : {},
1167
+ policy: checkPolicy(options.unfinishedPolicy)
1168
+ };
1169
+ this.dispatchBufferedUpdates();
1170
+ }
1171
+ dispatchBufferedUpdates() {
1172
+ const pending = this.bufferedUpdates;
1173
+ this.bufferedUpdates = [];
1174
+ for (const update of pending) {
1175
+ const entry = this.updateHandlerFor(update.name);
1176
+ if (entry) this.runUpdate(update, entry);
1177
+ else this.bufferedUpdates.push(update);
1178
+ }
1179
+ }
1180
+ handleQuery(query, handler) {
1181
+ const name = definitionName(query, "ctx.handleQuery");
1182
+ if (typeof handler !== "function")
1183
+ throw new TypeError("ctx.handleQuery needs a handler function");
1184
+ this.queryHandlers.set(name, handler);
1185
+ }
1186
+ setDefaultQueryHandler(handler) {
1187
+ if (typeof handler !== "function")
1188
+ throw new TypeError("ctx.setDefaultQueryHandler needs a handler function");
1189
+ this.defaultQueryHandler = handler;
1190
+ }
1191
+ /**
1192
+ * The drain check of 11 §2.18 when the workflow finishes: handlers still
1193
+ * running with policy `WARN_AND_ABANDON` give one warning line and
1194
+ * `unfinishedHandlers` in the result. A replay never reports them.
1195
+ */
1196
+ checkUnfinishedHandlers(command) {
1197
+ if (this.replaying || command.type === "CANCEL_WORKFLOW") return;
1198
+ const unfinished = [...this.running.values()].filter((entry) => entry.count > 0 && entry.policy === "WARN_AND_ABANDON").map(
1199
+ (entry) => ({ kind: entry.kind, name: entry.name, count: entry.count })
1200
+ ).sort(
1201
+ (a, b) => a.kind !== b.kind ? a.kind < b.kind ? -1 : 1 : a.name < b.name ? -1 : a.name > b.name ? 1 : 0
1202
+ );
1203
+ if (unfinished.length === 0) return;
1204
+ this.unfinished = unfinished;
1205
+ const total = unfinished.reduce((sum, entry) => sum + entry.count, 0);
1206
+ const what = unfinished.every((entry) => entry.kind === "signal") ? "signal handler" : unfinished.every((entry) => entry.kind === "update") ? "update handler" : "signal or update handler";
1207
+ this.logs.push({
1208
+ level: "warn",
1209
+ message: `The workflow finished while ${total} ${what}${total === 1 ? " was" : "s were"} still running (${unfinished.map((entry) => `${entry.name} x${entry.count}`).join(
1210
+ ", "
1211
+ )}); await ctx.allHandlersFinished() before returning, or set unfinishedPolicy: 'ABANDON' on the handler`,
1212
+ data: unfinished.map((entry) => ({ kind: entry.kind, name: entry.name, count: entry.count }))
1213
+ });
667
1214
  }
668
1215
  // ---- the workflow function ------------------------------------------------
669
1216
  startFunction(input) {
670
1217
  let promise;
671
1218
  try {
672
- promise = Promise.resolve(this.definition.fn(this.context, input));
1219
+ const run = (value) => (
1220
+ // The input crossed the history as JSON; the function declared its type.
1221
+ Promise.resolve(this.definition.fn(this.context, value))
1222
+ );
1223
+ promise = this.inbound.length === 0 ? run(input) : compose(
1224
+ this.inbound,
1225
+ "execute",
1226
+ (execute) => run(execute.input)
1227
+ )({ workflowFn: this.request.workflowFn, input, headers: { ...this.startHeaders } });
673
1228
  } catch (error) {
674
1229
  promise = Promise.reject(error);
675
1230
  }
@@ -699,7 +1254,13 @@ var WorkflowExecution = class {
699
1254
  onWorkflowError(error) {
700
1255
  if (this.closed || this.failure) return;
701
1256
  if (error instanceof ContinueAsNewRequest) {
702
- this.pushTerminal({ type: "CONTINUE_AS_NEW", input: error.input, options: error.options });
1257
+ const command = {
1258
+ type: "CONTINUE_AS_NEW",
1259
+ input: error.input,
1260
+ options: error.options
1261
+ };
1262
+ if (Object.keys(error.headers).length > 0) command.headers = { ...error.headers };
1263
+ this.pushTerminal(command);
703
1264
  return;
704
1265
  }
705
1266
  const name = errorName(error);
@@ -719,6 +1280,10 @@ var WorkflowExecution = class {
719
1280
  this.failTask("UNSUPPORTED_COMMAND", messageOf(error), stackTextOf(error));
720
1281
  return;
721
1282
  }
1283
+ if (error instanceof QueryHandlerSideEffect) {
1284
+ this.failTask("WORKFLOW_CODE_ERROR", messageOf(error), stackTextOf(error), void 0, name);
1285
+ return;
1286
+ }
722
1287
  if (this.cancelRequested && isCancellation(error)) {
723
1288
  this.pushTerminal({ type: "CANCEL_WORKFLOW" });
724
1289
  return;
@@ -753,6 +1318,7 @@ var WorkflowExecution = class {
753
1318
  }
754
1319
  /** Issue a command; false when the run is already closed (the command is dropped). */
755
1320
  push(command) {
1321
+ this.assertWritable(command.type);
756
1322
  if (this.closed || this.failure) return false;
757
1323
  this.checkLimit();
758
1324
  this.commands.push(command);
@@ -762,6 +1328,7 @@ var WorkflowExecution = class {
762
1328
  if (this.closed || this.failure) return;
763
1329
  this.commands.push(command);
764
1330
  this.closed = true;
1331
+ this.checkUnfinishedHandlers(command);
765
1332
  }
766
1333
  stack() {
767
1334
  return this.captureStacks ? captureStack() : void 0;
@@ -777,7 +1344,7 @@ var WorkflowExecution = class {
777
1344
  `${operation.nodeType} cannot run as a durable activity: only store and registry nodes and internal:http, internal:datatable, internal:set and internal:code can`,
778
1345
  { type: "durable.activityTypeNotAllowed", errorType: "VALIDATION", nonRetryable: true }
779
1346
  );
780
- const activityId = this.ids.next("a");
1347
+ const activityId = this.nextId("a");
781
1348
  const operationName = options.operation ?? operation.operation ?? "execute";
782
1349
  const nodeVersion = options.nodeVersion ?? operation.version;
783
1350
  const cancellationType = options.cancellationType ?? "WAIT_CANCELLATION_COMPLETED";
@@ -804,20 +1371,119 @@ var WorkflowExecution = class {
804
1371
  if (options.priority !== void 0) command.priority = options.priority;
805
1372
  if (options.summary !== void 0) command.summary = options.summary;
806
1373
  if (options.runOn !== void 0) command.runOn = options.runOn;
807
- if (options.headers !== void 0) command.headers = { ...options.headers };
1374
+ checkFairness(options);
1375
+ if (options.fairnessKey !== void 0) command.fairnessKey = options.fairnessKey;
1376
+ if (options.fairnessWeight !== void 0) command.fairnessWeight = options.fairnessWeight;
1377
+ return this.issueActivity(
1378
+ scope,
1379
+ command,
1380
+ options.headers,
1381
+ {
1382
+ description: `activity ${activityId} (${operation.nodeType} ${operationName})`,
1383
+ nodeType: operation.nodeType,
1384
+ operation: operationName,
1385
+ cancellationType
1386
+ },
1387
+ { kind: "node", name: operation.nodeType, operation: operationName }
1388
+ );
1389
+ }
1390
+ /**
1391
+ * `ctx.activity` (Phase C): a user activity on a self-hosted task queue
1392
+ * (`SCHEDULE_ACTIVITY` of kind `user`). The queue is the option, else the
1393
+ * declaration's, else the run's own.
1394
+ */
1395
+ userActivity(scope, activity, options) {
1396
+ if (typeof options !== "object" || options === null)
1397
+ throw new TypeError("ctx.activity needs options with args");
1398
+ let name;
1399
+ let declaredQueue;
1400
+ if (typeof activity === "string") name = activity;
1401
+ else if (isActivityDefinition(activity)) {
1402
+ name = activity.name;
1403
+ declaredQueue = activity.options.taskQueue;
1404
+ } else throw new TypeError("ctx.activity needs an activity name or a declareActivity() value");
1405
+ checkActivityName(name);
1406
+ const taskQueue = options.taskQueue ?? declaredQueue ?? this.info.taskQueue ?? "managed";
1407
+ checkTaskQueueName(taskQueue);
1408
+ const connections = options.connections;
1409
+ if (connections !== void 0 && (!Array.isArray(connections) || connections.length > MAX_ACTIVITY_CONNECTIONS || new Set(connections).size !== connections.length || !connections.every((entry) => typeof entry === "string" && entry !== "")))
1410
+ throw new TypeError(
1411
+ `connections must be at most ${MAX_ACTIVITY_CONNECTIONS} distinct connection names`
1412
+ );
1413
+ checkFairness(options);
1414
+ const activityId = this.nextId("a");
1415
+ const cancellationType = options.cancellationType ?? "WAIT_CANCELLATION_COMPLETED";
1416
+ const command = {
1417
+ type: "SCHEDULE_ACTIVITY",
1418
+ activityId,
1419
+ kind: "user",
1420
+ activityName: name,
1421
+ taskQueue,
1422
+ args: toJson(options.args, `The args of activity ${name}`),
1423
+ retry: explicitRetryPolicy(options.retry)
1424
+ };
1425
+ const startToClose = optionalMs(options.startToCloseTimeout, "startToCloseTimeout");
1426
+ const scheduleToClose = optionalMs(options.scheduleToCloseTimeout, "scheduleToCloseTimeout");
1427
+ const scheduleToStart = optionalMs(options.scheduleToStartTimeout, "scheduleToStartTimeout");
1428
+ const heartbeat = optionalMs(options.heartbeatTimeout, "heartbeatTimeout");
1429
+ if (startToClose !== void 0) command.startToCloseTimeout = startToClose;
1430
+ if (scheduleToClose !== void 0) command.scheduleToCloseTimeout = scheduleToClose;
1431
+ if (scheduleToStart !== void 0) command.scheduleToStartTimeout = scheduleToStart;
1432
+ if (heartbeat !== void 0) command.heartbeatTimeout = heartbeat;
1433
+ if (options.cancellationType !== void 0) command.cancellationType = options.cancellationType;
1434
+ if (options.priority !== void 0) command.priority = options.priority;
1435
+ if (options.summary !== void 0) command.summary = options.summary;
1436
+ if (options.fairnessKey !== void 0) command.fairnessKey = options.fairnessKey;
1437
+ if (options.fairnessWeight !== void 0) command.fairnessWeight = options.fairnessWeight;
1438
+ if (connections !== void 0) command.connections = [...connections];
1439
+ return this.issueActivity(
1440
+ scope,
1441
+ command,
1442
+ options.headers,
1443
+ {
1444
+ description: `activity ${activityId} (user ${name})`,
1445
+ nodeType: "",
1446
+ operation: name,
1447
+ activityName: name,
1448
+ cancellationType
1449
+ },
1450
+ { kind: "user", name }
1451
+ );
1452
+ }
1453
+ /** Run the outbound `scheduleActivity` interceptors, then issue the command. */
1454
+ issueActivity(scope, command, headers, pending, what) {
1455
+ if (this.outbound.length === 0) {
1456
+ if (headers !== void 0) command.headers = { ...headers };
1457
+ return this.pushActivity(scope, command, pending);
1458
+ }
1459
+ const chain = compose(
1460
+ this.outbound,
1461
+ "scheduleActivity",
1462
+ (input) => {
1463
+ command.args = toJson(input.args, `The args of ${what.name}`);
1464
+ if (headers !== void 0 || Object.keys(input.headers).length > 0)
1465
+ command.headers = { ...input.headers };
1466
+ return this.pushActivity(scope, command, pending);
1467
+ }
1468
+ );
1469
+ return chain({
1470
+ activityId: command.activityId,
1471
+ kind: what.kind,
1472
+ name: what.name,
1473
+ ...what.operation !== void 0 ? { operation: what.operation } : {},
1474
+ args: command.args,
1475
+ headers: { ...headers ?? {} }
1476
+ });
1477
+ }
1478
+ pushActivity(scope, command, pending) {
1479
+ const activityId = command.activityId;
1480
+ const cancellationType = pending.cancellationType;
808
1481
  if (scope.consideredCancelled)
809
1482
  return Promise.reject(new CancelledFailure("Scope is cancelled"));
810
1483
  if (!this.push(command)) return this.never();
811
1484
  this.issued.add(`activity:${activityId}`);
812
1485
  return new Promise((resolve, reject) => {
813
- const entry = {
814
- resolve,
815
- reject,
816
- description: `activity ${activityId} (${operation.nodeType} ${operationName})`,
817
- nodeType: operation.nodeType,
818
- operation: operationName,
819
- cancellationType
820
- };
1486
+ const entry = { ...pending, resolve, reject };
821
1487
  const stack = this.stack();
822
1488
  if (stack !== void 0) entry.stack = stack;
823
1489
  entry.release = scope.onCancel(() => {
@@ -827,7 +1493,8 @@ var WorkflowExecution = class {
827
1493
  activityId,
828
1494
  entry.nodeType,
829
1495
  entry.operation,
830
- new CancelledFailure("Activity cancelled")
1496
+ new CancelledFailure("Activity cancelled"),
1497
+ entry.activityName
831
1498
  );
832
1499
  if (cancellationType !== "ABANDON")
833
1500
  this.push({ type: "REQUEST_CANCEL_ACTIVITY", activityId });
@@ -839,6 +1506,124 @@ var WorkflowExecution = class {
839
1506
  this.activities.set(activityId, entry);
840
1507
  });
841
1508
  }
1509
+ /**
1510
+ * `ctx.localActivity` (11 §2.12). A replayed activation returns the outcome
1511
+ * its history recorded; the open activation takes the outcome the worker
1512
+ * passed in `localActivityResults`, or asks for the local activity
1513
+ * (`localActivityRequested`) and waits for the same task to run again.
1514
+ * Either way the outcome is recorded with `RECORD_LOCAL_ACTIVITY` at the
1515
+ * call, so a replay issues the same command at the same position.
1516
+ */
1517
+ localActivity(scope, operation, options) {
1518
+ if (typeof operation === "function")
1519
+ throw new PhaseCFeatureError("ctx.localActivity with a function (user local activities)");
1520
+ if (typeof operation !== "object" || operation === null || typeof operation.nodeType !== "string")
1521
+ throw new TypeError(
1522
+ "ctx.localActivity needs a node operation (from nodes.* or the generated clients)"
1523
+ );
1524
+ const descriptor = operation;
1525
+ if (descriptor.role === "trigger" || !isActivityNodeTypeAllowed(descriptor.nodeType))
1526
+ throw new ApplicationError(
1527
+ `${descriptor.nodeType} cannot run as a durable activity: only store and registry nodes and internal:http, internal:datatable, internal:set and internal:code can`,
1528
+ { type: "durable.activityTypeNotAllowed", errorType: "VALIDATION", nonRetryable: true }
1529
+ );
1530
+ if (typeof options !== "object" || options === null)
1531
+ throw new TypeError("ctx.localActivity needs options with args");
1532
+ const timeout = options.startToCloseTimeout === void 0 ? LOCAL_ACTIVITY_MAX_TIMEOUT_MS : toMs(options.startToCloseTimeout, "startToCloseTimeout");
1533
+ if (timeout < 1 || timeout > LOCAL_ACTIVITY_MAX_TIMEOUT_MS)
1534
+ throw new TypeError(
1535
+ `A local activity's startToCloseTimeout must be between 1 ms and 30 s (got ${timeout} ms); use ctx.run for longer calls`
1536
+ );
1537
+ const localActivityId = this.nextId("l");
1538
+ const operationName = options.operation ?? descriptor.operation ?? "execute";
1539
+ const nodeVersion = options.nodeVersion ?? descriptor.version;
1540
+ const retry = explicitRetryPolicy(options.retry);
1541
+ const args = toJson(options.args, `The args of ${descriptor.nodeType}`);
1542
+ const proceed = (args2) => {
1543
+ if (scope.consideredCancelled)
1544
+ return Promise.reject(new CancelledFailure("Scope is cancelled"));
1545
+ const label = `local activity ${localActivityId} (${descriptor.nodeType} ${operationName})`;
1546
+ const wrap = (cause) => new ActivityFailure(
1547
+ `Local activity ${localActivityId} (${descriptor.nodeType} ${operationName}) failed: ${cause.message}`,
1548
+ localActivityId,
1549
+ descriptor.nodeType,
1550
+ operationName,
1551
+ cause
1552
+ );
1553
+ const record = (outcome) => {
1554
+ const command = {
1555
+ type: "RECORD_LOCAL_ACTIVITY",
1556
+ localActivityId,
1557
+ attempts: Math.max(1, Math.round(outcome.attempts)),
1558
+ nodeType: descriptor.nodeType,
1559
+ operation: operationName,
1560
+ ...outcome.failure !== void 0 ? { failure: outcome.failure } : { result: outcome.result ?? null }
1561
+ };
1562
+ if (options.summary !== void 0) command.summary = options.summary;
1563
+ if (!this.push(command)) return this.never();
1564
+ if (outcome.failure !== void 0)
1565
+ return Promise.reject(wrap(failureToError(outcome.failure)));
1566
+ return Promise.resolve(outcome.result ?? null);
1567
+ };
1568
+ const replayed = this.replaying;
1569
+ if (replayed) {
1570
+ const recorded = this.recordedLocalActivities.get(localActivityId);
1571
+ if (recorded !== void 0) return record(recorded);
1572
+ }
1573
+ const given = replayed ? void 0 : this.localActivityResults.get(localActivityId);
1574
+ let attempt = 1;
1575
+ let backoffMs = 0;
1576
+ if (given !== void 0) {
1577
+ if (given.failure === void 0) return record(given);
1578
+ const decision = computeRetryDelay(retry, given.attempts, given.failure);
1579
+ if (!decision.retry) return record(given);
1580
+ attempt = given.attempts + 1;
1581
+ backoffMs = decision.delayMs;
1582
+ }
1583
+ const request = {
1584
+ localActivityId,
1585
+ kind: "node",
1586
+ nodeType: descriptor.nodeType,
1587
+ operation: operationName,
1588
+ args: args2,
1589
+ retry,
1590
+ startToCloseTimeout: timeout,
1591
+ attempt,
1592
+ backoffMs
1593
+ };
1594
+ if (nodeVersion !== void 0) request.nodeVersion = nodeVersion;
1595
+ if (options.connection !== void 0) request.connection = options.connection;
1596
+ if (options.summary !== void 0) request.summary = options.summary;
1597
+ return new Promise((resolve, reject) => {
1598
+ const entry = {
1599
+ resolve,
1600
+ reject,
1601
+ description: label,
1602
+ request,
1603
+ replayed
1604
+ };
1605
+ const stack = this.stack();
1606
+ if (stack !== void 0) entry.stack = stack;
1607
+ entry.release = scope.onCancel(() => {
1608
+ if (!this.localActivities.delete(localActivityId)) return;
1609
+ reject(wrap(new CancelledFailure("Local activity cancelled")));
1610
+ });
1611
+ this.localActivities.set(localActivityId, entry);
1612
+ });
1613
+ };
1614
+ if (this.outbound.length === 0) return proceed(args);
1615
+ const chain = compose(
1616
+ this.outbound,
1617
+ "scheduleLocalActivity",
1618
+ (input) => proceed(toJson(input.args, `The args of ${descriptor.nodeType}`))
1619
+ );
1620
+ return chain({
1621
+ localActivityId,
1622
+ nodeType: descriptor.nodeType,
1623
+ operation: operationName,
1624
+ args
1625
+ });
1626
+ }
842
1627
  startTimer(scope, durationMs, summary) {
843
1628
  return this.timer(scope, durationMs, summary).promise;
844
1629
  }
@@ -847,7 +1632,7 @@ var WorkflowExecution = class {
847
1632
  if (durationMs <= 0) return { promise: Promise.resolve() };
848
1633
  if (scope.consideredCancelled)
849
1634
  return { promise: Promise.reject(new CancelledFailure("Scope is cancelled")) };
850
- const timerId = this.ids.next("t");
1635
+ const timerId = this.nextId("t");
851
1636
  const command = {
852
1637
  type: "START_TIMER",
853
1638
  timerId,
@@ -960,13 +1745,16 @@ var WorkflowExecution = class {
960
1745
  });
961
1746
  });
962
1747
  }
963
- onSignal(signal, handler) {
1748
+ onSignal(signal, handler, options = {}) {
1749
+ if (typeof handler !== "function") throw new TypeError("ctx.onSignal needs a handler function");
1750
+ const policy = checkPolicy(options.unfinishedPolicy);
964
1751
  const name = signalName(signal);
965
1752
  this.signalHandlers.set(name, handler);
1753
+ this.signalPolicies.set(name, policy);
966
1754
  let payload = this.takeBuffered(name);
967
1755
  while (payload !== void 0) {
968
1756
  const value = payload;
969
- this.runHandler(() => handler(value));
1757
+ this.runHandler(() => handler(value), name, policy);
970
1758
  payload = this.takeBuffered(name);
971
1759
  }
972
1760
  }
@@ -976,8 +1764,36 @@ var WorkflowExecution = class {
976
1764
  for (const entry of buffered) {
977
1765
  if (this.signalHandlers.has(entry.name)) continue;
978
1766
  const payload = this.takeBuffered(entry.name);
979
- if (payload !== void 0) this.runHandler(() => handler(entry.name, payload));
1767
+ if (payload !== void 0)
1768
+ this.runHandler(() => handler(entry.name, payload), entry.name, this.defaultSignalPolicy);
1769
+ }
1770
+ }
1771
+ /** The command target of a run handle (11 §2.1, §2.24). */
1772
+ externalTarget(target) {
1773
+ if (typeof target === "string") {
1774
+ if (target === "") throw new TypeError("A run handle needs a run id");
1775
+ return { runId: target };
1776
+ }
1777
+ if (typeof target === "object" && target !== null) {
1778
+ if ("runId" in target && typeof target.runId === "string" && target.runId !== "")
1779
+ return { runId: target.runId };
1780
+ if ("workflowKey" in target && typeof target.workflowKey === "string" && target.workflowKey !== "" && typeof target.workflowId === "string" && target.workflowId !== "") {
1781
+ const out = { workflowKey: target.workflowKey, workflowId: target.workflowId };
1782
+ if (typeof target.firstRunId === "string" && target.firstRunId !== "")
1783
+ out.firstRunId = target.firstRunId;
1784
+ return out;
1785
+ }
980
1786
  }
1787
+ throw new TypeError("A run handle needs a run id or { workflowKey, workflowId }");
1788
+ }
1789
+ runHandle(target) {
1790
+ const resolved = this.externalTarget(target);
1791
+ return {
1792
+ runId: "runId" in resolved ? resolved.runId : "",
1793
+ ..."workflowId" in resolved ? { workflowKey: resolved.workflowKey, workflowId: resolved.workflowId } : {},
1794
+ signal: (signal, payload) => this.signalExternal(resolved, signalName(signal), payload),
1795
+ cancel: () => this.cancelExternal(resolved)
1796
+ };
981
1797
  }
982
1798
  childTarget(target) {
983
1799
  if (typeof target === "string") {
@@ -1004,6 +1820,12 @@ var WorkflowExecution = class {
1004
1820
  if (options.details !== void 0) opts["details"] = options.details;
1005
1821
  if (options.context !== void 0)
1006
1822
  opts["context"] = toJson(options.context, "The child context");
1823
+ if (options.retry !== void 0)
1824
+ opts["retry"] = toJson(runRetryPolicyOf(options.retry), "The child retry policy");
1825
+ if (options.versioningBehavior !== void 0)
1826
+ opts["versioningBehavior"] = options.versioningBehavior;
1827
+ if (options.idReusePolicy !== void 0) opts["idReusePolicy"] = options.idReusePolicy;
1828
+ if (options.idConflictPolicy !== void 0) opts["idConflictPolicy"] = options.idConflictPolicy;
1007
1829
  const durations = [
1008
1830
  ["startDelay", "startDelay"],
1009
1831
  ["executionTimeout", "executionTimeout"],
@@ -1025,13 +1847,39 @@ var WorkflowExecution = class {
1025
1847
  ...opts
1026
1848
  }
1027
1849
  };
1850
+ checkFairness(options);
1851
+ if (options.fairnessKey !== void 0) command.options.fairnessKey = options.fairnessKey;
1852
+ if (options.fairnessWeight !== void 0)
1853
+ command.options.fairnessWeight = options.fairnessWeight;
1028
1854
  if (options.version !== void 0) command.version = options.version;
1029
1855
  if (options.headers !== void 0) command.headers = { ...options.headers };
1030
1856
  return command;
1031
1857
  }
1858
+ /**
1859
+ * Run the outbound `startChild` interceptors around a child start. `go`
1860
+ * starts the child with the options the interceptors leave (input and
1861
+ * headers).
1862
+ */
1863
+ interceptChild(target, options, go) {
1864
+ if (this.outbound.length === 0) return go(options);
1865
+ const chain = compose(
1866
+ this.outbound,
1867
+ "startChild",
1868
+ (input) => go({
1869
+ ...options,
1870
+ input: input.input,
1871
+ ...options.headers !== void 0 || Object.keys(input.headers).length > 0 ? { headers: { ...input.headers } } : {}
1872
+ })
1873
+ );
1874
+ return chain({
1875
+ ...this.childTarget(target),
1876
+ input: toJson(options.input, "The child input"),
1877
+ headers: { ...options.headers ?? {} }
1878
+ });
1879
+ }
1032
1880
  startChildRun(scope, target, options, defaultPolicy) {
1033
1881
  if (scope.consideredCancelled) return void 0;
1034
- const childId = this.ids.next("c");
1882
+ const childId = this.nextId("c");
1035
1883
  const command = this.childCommand(childId, target, options, defaultPolicy);
1036
1884
  if (!this.push(command)) return void 0;
1037
1885
  this.issued.add(`child:${childId}`);
@@ -1056,7 +1904,7 @@ var WorkflowExecution = class {
1056
1904
  entry.reject(error);
1057
1905
  return;
1058
1906
  }
1059
- const externalId = this.ids.next("x");
1907
+ const externalId = this.nextId("x");
1060
1908
  if (this.push({
1061
1909
  type: "REQUEST_CANCEL_EXTERNAL_RUN",
1062
1910
  externalId,
@@ -1090,15 +1938,17 @@ var WorkflowExecution = class {
1090
1938
  };
1091
1939
  }
1092
1940
  child(scope, target, options) {
1093
- const child = this.startChildRun(
1094
- scope,
1095
- target,
1096
- options,
1097
- CHILD_DEFAULT_PARENT_CLOSE_POLICY.child
1098
- );
1099
- if (!child)
1100
- return scope.consideredCancelled ? Promise.reject(new CancelledFailure("Scope is cancelled")) : this.never();
1101
- return this.childResult(child);
1941
+ return this.interceptChild(target, options, (opts) => {
1942
+ const child = this.startChildRun(
1943
+ scope,
1944
+ target,
1945
+ opts,
1946
+ CHILD_DEFAULT_PARENT_CLOSE_POLICY.child
1947
+ );
1948
+ if (!child)
1949
+ return scope.consideredCancelled ? Promise.reject(new CancelledFailure("Scope is cancelled")) : this.never();
1950
+ return this.childResult(child);
1951
+ });
1102
1952
  }
1103
1953
  childResult(child) {
1104
1954
  if (child.settled && child.outcome) {
@@ -1118,6 +1968,13 @@ var WorkflowExecution = class {
1118
1968
  });
1119
1969
  }
1120
1970
  startChild(scope, target, options) {
1971
+ return this.interceptChild(
1972
+ target,
1973
+ options,
1974
+ (opts) => this.startChildHandle(scope, target, opts)
1975
+ );
1976
+ }
1977
+ startChildHandle(scope, target, options) {
1121
1978
  const child = this.startChildRun(
1122
1979
  scope,
1123
1980
  target,
@@ -1147,7 +2004,21 @@ var WorkflowExecution = class {
1147
2004
  });
1148
2005
  }
1149
2006
  signalExternal(target, name, payload) {
1150
- const externalId = this.ids.next("x");
2007
+ if (this.outbound.length === 0) return this.pushSignal(target, name, payload, void 0);
2008
+ const chain = compose(
2009
+ this.outbound,
2010
+ "signalRun",
2011
+ (input) => this.pushSignal(input.target, input.name, input.payload, input.headers)
2012
+ );
2013
+ return chain({
2014
+ target,
2015
+ name,
2016
+ payload: toJson(payload, `The payload of signal ${name}`),
2017
+ headers: {}
2018
+ });
2019
+ }
2020
+ pushSignal(target, name, payload, headers) {
2021
+ const externalId = this.nextId("x");
1151
2022
  const command = {
1152
2023
  type: "SIGNAL_RUN",
1153
2024
  externalId,
@@ -1155,6 +2026,7 @@ var WorkflowExecution = class {
1155
2026
  name,
1156
2027
  payload: toJson(payload, `The payload of signal ${name}`)
1157
2028
  };
2029
+ if (headers !== void 0 && Object.keys(headers).length > 0) command.headers = { ...headers };
1158
2030
  if (!this.push(command)) return this.never();
1159
2031
  this.issued.add(`external:${externalId}`);
1160
2032
  return new Promise((resolve, reject) => {
@@ -1169,7 +2041,15 @@ var WorkflowExecution = class {
1169
2041
  });
1170
2042
  }
1171
2043
  cancelExternal(target) {
1172
- const externalId = this.ids.next("x");
2044
+ if (this.outbound.length === 0) return this.pushCancel(target);
2045
+ return compose(
2046
+ this.outbound,
2047
+ "cancelRun",
2048
+ (input) => this.pushCancel(input.target)
2049
+ )({ target });
2050
+ }
2051
+ pushCancel(target) {
2052
+ const externalId = this.nextId("x");
1173
2053
  if (!this.push({ type: "REQUEST_CANCEL_EXTERNAL_RUN", externalId, target }))
1174
2054
  return this.never();
1175
2055
  this.issued.add(`external:${externalId}`);
@@ -1185,7 +2065,15 @@ var WorkflowExecution = class {
1185
2065
  });
1186
2066
  }
1187
2067
  sideEffect(fn) {
1188
- const sideEffectId = this.ids.next("s");
2068
+ if (this.outbound.length === 0) return this.recordSideEffect(fn);
2069
+ return compose(
2070
+ this.outbound,
2071
+ "sideEffect",
2072
+ () => this.recordSideEffect(fn)
2073
+ )({});
2074
+ }
2075
+ recordSideEffect(fn) {
2076
+ const sideEffectId = this.nextId("s");
1189
2077
  let value;
1190
2078
  if (this.replaying && this.recordedSideEffects.has(sideEffectId)) {
1191
2079
  value = this.recordedSideEffects.get(sideEffectId) ?? null;
@@ -1199,6 +2087,7 @@ var WorkflowExecution = class {
1199
2087
  if (typeof patchId !== "string" || patchId === "") throw new TypeError("A patch needs an id");
1200
2088
  const known = this.patches.get(patchId);
1201
2089
  if (known !== void 0) return known;
2090
+ this.assertWritable(deprecated ? "ctx.deprecatePatch()" : "ctx.patched()");
1202
2091
  const markerId = patchMarkerId(patchId);
1203
2092
  let result;
1204
2093
  if (this.replaying) {
@@ -1225,25 +2114,120 @@ var WorkflowExecution = class {
1225
2114
  }
1226
2115
  setCurrentDetails(markdown) {
1227
2116
  if (typeof markdown !== "string") throw new TypeError("setCurrentDetails takes Markdown text");
2117
+ this.assertWritable("ctx.setCurrentDetails()");
1228
2118
  this.currentDetails = markdown;
1229
2119
  this.push({
1230
2120
  type: "RECORD_MARKER",
1231
- markerId: this.ids.next("m"),
2121
+ markerId: this.nextId("m"),
1232
2122
  data: { currentDetails: markdown }
1233
2123
  });
1234
2124
  }
1235
2125
  upsertContext(entries) {
1236
- this.push({
1237
- type: "UPSERT_CONTEXT",
1238
- entries: toJson(entries, "The context entries")
2126
+ const json = toJson(entries, "The context entries");
2127
+ if (!isJsonObject(json)) throw new TypeError("ctx.upsertContext takes an object");
2128
+ const apply = (input) => {
2129
+ const final = toJson(input.entries, "The context entries");
2130
+ if (!isJsonObject(final)) throw new TypeError("ctx.upsertContext takes an object");
2131
+ if (this.push({ type: "UPSERT_CONTEXT", entries: final }))
2132
+ this.memo = { ...this.memo, ...final };
2133
+ };
2134
+ if (this.outbound.length === 0) apply({ entries: json });
2135
+ else
2136
+ compose(
2137
+ this.outbound,
2138
+ "upsertContext",
2139
+ apply
2140
+ )({ entries: json });
2141
+ }
2142
+ /** `ctx.upsertSearchAttributes` (Phase C): a null value removes an attribute. */
2143
+ upsertSearchAttributes(attributes) {
2144
+ const json = toJson(attributes, "The search attributes");
2145
+ if (!isJsonObject(json) || Object.keys(json).length === 0)
2146
+ throw new TypeError("ctx.upsertSearchAttributes takes an object with at least one attribute");
2147
+ for (const key of Object.keys(json))
2148
+ if (key.trim() === "") throw new TypeError("A search attribute needs a name");
2149
+ const apply = (input) => {
2150
+ const final = toJson(input.attributes, "The search attributes");
2151
+ if (!isJsonObject(final)) throw new TypeError("ctx.upsertSearchAttributes takes an object");
2152
+ if (this.push({ type: "UPSERT_SEARCH_ATTRIBUTES", attributes: final }))
2153
+ this.searchAttributes = mergeSearchAttributes(this.searchAttributes, final);
2154
+ };
2155
+ if (this.outbound.length === 0) apply({ attributes: json });
2156
+ else
2157
+ compose(
2158
+ this.outbound,
2159
+ "upsertSearchAttributes",
2160
+ apply
2161
+ )({ attributes: json });
2162
+ }
2163
+ /** A `ctx.sinks.<sink>.<fn>(...args)` call: recorded once, never on replay (11 §2.31). */
2164
+ callSink(sink, fn, args) {
2165
+ this.assertWritable(`The sink call ${sink}.${fn}`);
2166
+ if (this.replaying || this.closed || this.failure || this.request.kind !== "task") return;
2167
+ if (this.sinkCalls.length >= MAX_SINK_CALLS) {
2168
+ if (!this.sinkCallsDropped) {
2169
+ this.sinkCallsDropped = true;
2170
+ this.logs.push({
2171
+ level: "warn",
2172
+ message: `More than ${MAX_SINK_CALLS} sink calls in one workflow task; the rest were dropped`
2173
+ });
2174
+ }
2175
+ return;
2176
+ }
2177
+ this.sinkCalls.push({
2178
+ sink,
2179
+ fn,
2180
+ args: args.map((arg, index) => toJson(arg, `Argument ${index + 1} of ${sink}.${fn}`))
1239
2181
  });
1240
2182
  }
2183
+ sinksProxy() {
2184
+ const execution = this;
2185
+ const sinkOf = (sink) => new Proxy(
2186
+ {},
2187
+ {
2188
+ get(_target, fn) {
2189
+ if (typeof fn !== "string" || fn === "then") return void 0;
2190
+ return (...args) => execution.callSink(sink, fn, args);
2191
+ }
2192
+ }
2193
+ );
2194
+ return new Proxy(
2195
+ {},
2196
+ {
2197
+ get(_target, sink) {
2198
+ if (typeof sink !== "string" || sink === "then") return void 0;
2199
+ return sinkOf(sink);
2200
+ }
2201
+ }
2202
+ );
2203
+ }
2204
+ /** `ctx.continueAsNew`: a rejection with the request, after the outbound interceptors. */
2205
+ continueAsNewCall(input, options) {
2206
+ let request;
2207
+ try {
2208
+ request = this.continueAsNew(input, options);
2209
+ } catch (error) {
2210
+ return Promise.reject(error);
2211
+ }
2212
+ if (this.outbound.length === 0) return Promise.reject(request);
2213
+ return compose(
2214
+ this.outbound,
2215
+ "continueAsNew",
2216
+ (changed) => Promise.reject(
2217
+ new ContinueAsNewRequest(
2218
+ toJson(changed.input, "The continue-as-new input"),
2219
+ changed.options,
2220
+ headersOf(changed.headers)
2221
+ )
2222
+ )
2223
+ )({ input: request.input, options: { ...request.options }, headers: {} });
2224
+ }
1241
2225
  /** The request a `ctx.continueAsNew` call throws; the run ends with `CONTINUE_AS_NEW`. */
1242
2226
  continueAsNew(input, options) {
1243
2227
  const opts = {};
1244
2228
  if (options.taskQueue !== void 0) opts["taskQueue"] = options.taskQueue;
1245
2229
  if (options.retry !== void 0)
1246
- opts["retry"] = toJson(explicitRetryPolicy(options.retry), "retry");
2230
+ opts["retry"] = toJson(continueAsNewRetryOf(options.retry), "retry");
1247
2231
  if (options.workflowTaskTimeout !== void 0)
1248
2232
  opts["workflowTaskTimeout"] = toMs(options.workflowTaskTimeout, "workflowTaskTimeout");
1249
2233
  if (options.context !== void 0) opts["context"] = toJson(options.context, "context");
@@ -1292,6 +2276,7 @@ var WorkflowExecution = class {
1292
2276
  out.push({ description: entry.description ?? fallback, stack: entry.stack ?? "" });
1293
2277
  };
1294
2278
  for (const entry of this.activities.values()) add(entry, "activity");
2279
+ for (const entry of this.localActivities.values()) add(entry, "local activity");
1295
2280
  for (const entry of this.timers.values()) add(entry, "timer");
1296
2281
  for (const child of this.children.values()) {
1297
2282
  add(child.started, "child start");
@@ -1309,13 +2294,49 @@ var WorkflowExecution = class {
1309
2294
  );
1310
2295
  return out;
1311
2296
  }
1312
- answerQueries() {
1313
- return (this.request.queries ?? []).map((query) => {
1314
- if (!BUILT_IN_QUERIES.includes(query.name))
2297
+ /**
2298
+ * Answer a custom query (Phase C): the handler registered for the name, else
2299
+ * the default handler, run read-only through the inbound `handleQuery`
2300
+ * interceptors. No handler gives `rejected`.
2301
+ */
2302
+ answerCustomQuery(query) {
2303
+ if (this.failure)
2304
+ return { queryId: query.queryId, error: `The replay failed: ${this.failure.message}` };
2305
+ const own = this.queryHandlers.get(query.name);
2306
+ const fallback = this.defaultQueryHandler;
2307
+ const handler = own ?? (fallback !== void 0 ? (args) => fallback(query.name, args) : void 0);
2308
+ if (handler === void 0)
2309
+ return {
2310
+ queryId: query.queryId,
2311
+ rejected: true,
2312
+ error: `The workflow has no handler for query "${query.name}"`
2313
+ };
2314
+ const chain = compose(
2315
+ this.inbound,
2316
+ "handleQuery",
2317
+ (input) => handler(input.args)
2318
+ );
2319
+ try {
2320
+ const value = this.readOnlyCall(
2321
+ `the handler of query ${query.name}`,
2322
+ () => chain({ queryId: query.queryId, name: query.name, args: query.args ?? null })
2323
+ );
2324
+ if (typeof value === "object" && value !== null && typeof value.then === "function")
1315
2325
  return {
1316
2326
  queryId: query.queryId,
1317
- error: `Unknown query ${String(query.name)}; custom queries are Phase C`
2327
+ error: `The handler of query "${query.name}" returned a promise; query handlers must return a value`
1318
2328
  };
2329
+ return { queryId: query.queryId, value: toJson(value, `The result of query ${query.name}`) };
2330
+ } catch (error) {
2331
+ return {
2332
+ queryId: query.queryId,
2333
+ error: `${errorName(error) || "Error"}: ${messageOf(error)}`
2334
+ };
2335
+ }
2336
+ }
2337
+ answerQueries() {
2338
+ return (this.request.queries ?? []).map((query) => {
2339
+ if (!isBuiltInQuery(query.name)) return this.answerCustomQuery(query);
1319
2340
  if (this.failure)
1320
2341
  return { queryId: query.queryId, error: `The replay failed: ${this.failure.message}` };
1321
2342
  if (query.name === "__stack_trace")
@@ -1363,9 +2384,20 @@ ${entry.stack}`).join("\n\n")
1363
2384
  contextFor(bound) {
1364
2385
  const scope = () => bound ?? this.currentScope();
1365
2386
  const log = (level) => (message, data) => {
1366
- if (this.replaying) return;
2387
+ if (this.replaying || this.readOnly !== void 0) return;
1367
2388
  const line = { level, message: String(message) };
1368
- if (data !== void 0) line.data = toJson(data, "The log data");
2389
+ const payload = data !== void 0 ? toJson(data, "The log data") : void 0;
2390
+ const attributes = this.outbound.length === 0 ? {} : compose(
2391
+ this.outbound,
2392
+ "getLogAttributes",
2393
+ (input) => input
2394
+ )({});
2395
+ if (Object.keys(attributes).length === 0) {
2396
+ if (payload !== void 0) line.data = payload;
2397
+ } else {
2398
+ const extra = toJson(attributes, "The log attributes");
2399
+ line.data = payload === void 0 ? extra : isJsonObject(payload) && isJsonObject(extra) ? { ...extra, ...payload } : { ...isJsonObject(extra) ? extra : {}, data: payload };
2400
+ }
1369
2401
  this.logs.push(line);
1370
2402
  };
1371
2403
  const logger = {
@@ -1376,31 +2408,42 @@ ${entry.stack}`).join("\n\n")
1376
2408
  };
1377
2409
  const scopeFn = ((fn, options = {}) => this.cancellationScope(scope(), fn, options));
1378
2410
  scopeFn.current = () => scope();
1379
- const phaseC = (feature) => () => {
1380
- throw new PhaseCFeatureError(feature);
1381
- };
1382
2411
  const context = {
1383
2412
  info: this.info,
1384
2413
  log: logger,
1385
2414
  run: (operation, options) => this.scheduleActivity(scope(), operation, options),
1386
- sleep: (duration, options = {}) => this.startTimer(scope(), toMs(duration, "sleep duration"), options.summary),
2415
+ sleep: (duration, options = {}) => {
2416
+ const durationMs = toMs(duration, "sleep duration");
2417
+ if (this.outbound.length === 0)
2418
+ return this.startTimer(scope(), durationMs, options.summary);
2419
+ const current = scope();
2420
+ return compose(
2421
+ this.outbound,
2422
+ "startTimer",
2423
+ (input) => this.startTimer(current, toMs(input.durationMs, "sleep duration"), input.summary)
2424
+ )({ durationMs, ...options.summary !== void 0 ? { summary: options.summary } : {} });
2425
+ },
1387
2426
  condition: (predicate, timeout) => this.condition(scope(), predicate, timeout),
1388
2427
  signalReceived: (signal) => this.signalReceived(scope(), signal),
1389
- onSignal: (signal, handler) => this.onSignal(signal, handler),
2428
+ onSignal: ((signal, optionsOrHandler, maybeHandler) => {
2429
+ if (typeof optionsOrHandler === "function") this.onSignal(signal, optionsOrHandler);
2430
+ else if (maybeHandler !== void 0)
2431
+ this.onSignal(signal, maybeHandler, optionsOrHandler ?? {});
2432
+ else throw new TypeError("ctx.onSignal needs a handler function");
2433
+ }),
1390
2434
  setDefaultSignalHandler: (handler) => this.setDefaultSignalHandler(handler),
1391
2435
  child: (target, options = {}) => this.child(scope(), target, options),
1392
2436
  startChild: (target, options = {}) => this.startChild(scope(), target, options),
1393
- signalRun: (runId, signal, payload) => this.signalExternal({ runId }, signalName(signal), payload),
2437
+ signalRun: (target, signal, payload) => this.signalExternal(this.externalTarget(target), signalName(signal), payload),
1394
2438
  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 }),
2439
+ getRunHandle: (target) => this.runHandle(target),
2440
+ cancelRun: (target) => this.cancelExternal(this.externalTarget(target)),
1401
2441
  now: () => this.clock,
1402
2442
  random: () => this.random(),
1403
- uuid: () => uuidFrom(this.random),
2443
+ uuid: () => {
2444
+ this.assertWritable("ctx.uuid()");
2445
+ return uuidFrom(this.randomSource);
2446
+ },
1404
2447
  sideEffect: (fn) => this.sideEffect(fn),
1405
2448
  patched: (patchId) => this.patched(patchId, false),
1406
2449
  deprecatePatch: (patchId) => {
@@ -1408,13 +2451,7 @@ ${entry.stack}`).join("\n\n")
1408
2451
  },
1409
2452
  setCurrentDetails: (markdown) => this.setCurrentDetails(markdown),
1410
2453
  upsertContext: (entries) => this.upsertContext(entries),
1411
- continueAsNew: (input, options = {}) => {
1412
- try {
1413
- return Promise.reject(this.continueAsNew(input, options));
1414
- } catch (error) {
1415
- return Promise.reject(error);
1416
- }
1417
- },
2454
+ continueAsNew: (input, options = {}) => this.continueAsNewCall(input, options),
1418
2455
  cancellationScope: scopeFn,
1419
2456
  nonCancellable: (fn) => this.cancellationScope(scope(), fn, { nonCancellable: true }),
1420
2457
  onCancel: (fn) => {
@@ -1450,13 +2487,14 @@ ${entry.stack}`).join("\n\n")
1450
2487
  };
1451
2488
  },
1452
2489
  allHandlersFinished: () => this.allHandlersFinished(),
1453
- activity: phaseC("ctx.activity (user activities)"),
1454
- localActivity: phaseC(
1455
- "ctx.localActivity (local activities)"
1456
- ),
1457
- handleQuery: phaseC("ctx.handleQuery (custom queries)"),
1458
- handleUpdate: phaseC("ctx.handleUpdate (updates)"),
1459
- upsertSearchAttributes: phaseC("ctx.upsertSearchAttributes (search attributes)")
2490
+ activity: (activity, options) => this.userActivity(scope(), activity, options),
2491
+ localActivity: ((operation, options) => this.localActivity(scope(), operation, options)),
2492
+ handleQuery: ((query, handler) => this.handleQuery(query, handler)),
2493
+ setDefaultQueryHandler: (handler) => this.setDefaultQueryHandler(handler),
2494
+ handleUpdate: ((update, handler, options) => this.handleUpdate(update, handler, options)),
2495
+ setDefaultUpdateHandler: ((first, second) => this.setDefaultUpdateHandler(first, second)),
2496
+ upsertSearchAttributes: (attributes) => this.upsertSearchAttributes(attributes),
2497
+ sinks: this.sinksProxy()
1460
2498
  };
1461
2499
  return Object.freeze(context);
1462
2500
  }
@@ -1468,17 +2506,36 @@ function buildWorkflowRegistry(modules) {
1468
2506
  const byName = /* @__PURE__ */ new Map();
1469
2507
  const exportNames = /* @__PURE__ */ new Map();
1470
2508
  const ambiguous = /* @__PURE__ */ new Set();
2509
+ const interceptors = [];
2510
+ let dynamic;
2511
+ let dynamicAmbiguous = false;
1471
2512
  for (const [, namespace] of modules) {
1472
2513
  for (const exportName of Object.keys(namespace).sort()) {
1473
2514
  const value = namespace[exportName];
2515
+ if (exportName === "interceptors") {
2516
+ const factory = interceptorsFactoryOf(value);
2517
+ if (factory !== void 0 && !interceptors.includes(factory)) interceptors.push(factory);
2518
+ continue;
2519
+ }
1474
2520
  if (!isDurableWorkflow(value)) continue;
1475
2521
  const existing = byName.get(exportName);
1476
2522
  if (existing && existing !== value) ambiguous.add(exportName);
1477
2523
  byName.set(exportName, value);
1478
2524
  if (!exportNames.has(value)) exportNames.set(value, exportName);
2525
+ if (value.dynamic === true) {
2526
+ if (dynamic !== void 0 && dynamic !== value) dynamicAmbiguous = true;
2527
+ dynamic ??= value;
2528
+ }
1479
2529
  }
1480
2530
  }
1481
- return { byName, exportNames, ambiguous };
2531
+ return {
2532
+ byName,
2533
+ exportNames,
2534
+ ambiguous,
2535
+ ...dynamic !== void 0 ? { dynamic } : {},
2536
+ ...dynamicAmbiguous ? { dynamicAmbiguous } : {},
2537
+ ...interceptors.length > 0 ? { interceptors } : {}
2538
+ };
1482
2539
  }
1483
2540
  function failedResult(cause, message) {
1484
2541
  return {
@@ -1507,6 +2564,23 @@ function checkRequest(value) {
1507
2564
  }
1508
2565
  if (typeof value["nowMs"] !== "number" || !Number.isFinite(value["nowMs"]))
1509
2566
  return "nowMs must be a number";
2567
+ const updates = value["updates"];
2568
+ if (updates !== void 0) {
2569
+ if (!Array.isArray(updates)) return "updates must be an array";
2570
+ for (const [index, update] of updates.entries())
2571
+ if (!isObject(update) || typeof update["updateId"] !== "string" || update["updateId"] === "" || typeof update["name"] !== "string")
2572
+ return `updates[${index}] needs an updateId and a name`;
2573
+ }
2574
+ const queries = value["queries"];
2575
+ if (queries !== void 0) {
2576
+ if (!Array.isArray(queries)) return "queries must be an array";
2577
+ for (const [index, query] of queries.entries())
2578
+ if (!isObject(query) || typeof query["queryId"] !== "string" || typeof query["name"] !== "string")
2579
+ return `queries[${index}] needs a queryId and a name`;
2580
+ }
2581
+ const features = value["features"];
2582
+ if (features !== void 0 && (!Array.isArray(features) || !features.every((feature) => typeof feature === "string")))
2583
+ return "features must be an array of strings";
1510
2584
  return value;
1511
2585
  }
1512
2586
  function createWorkflowRuntime(registry, shims = void 0) {
@@ -1523,6 +2597,7 @@ function createWorkflowRuntime(registry, shims = void 0) {
1523
2597
  return Object.freeze({
1524
2598
  protocol: HISTORY_PROTOCOL,
1525
2599
  sdkVersion: SDK_VERSION,
2600
+ capabilities: Object.freeze([...RUNTIME_CAPABILITIES]),
1526
2601
  start(requestJson) {
1527
2602
  shims?.detach();
1528
2603
  execution = void 0;
@@ -1556,7 +2631,15 @@ function createWorkflowRuntime(registry, shims = void 0) {
1556
2631
  );
1557
2632
  return;
1558
2633
  }
1559
- const definition = registry.byName.get(request.workflowFn);
2634
+ const exported = registry.byName.get(request.workflowFn);
2635
+ if (!exported && registry.dynamic !== void 0 && registry.dynamicAmbiguous === true) {
2636
+ early = failedResult(
2637
+ "INVALID_REQUEST",
2638
+ `More than one module exports a dynamic workflow; "${request.workflowFn}" cannot be served`
2639
+ );
2640
+ return;
2641
+ }
2642
+ const definition = exported ?? registry.dynamic;
1560
2643
  if (!definition) {
1561
2644
  early = failedResult(
1562
2645
  "INVALID_REQUEST",
@@ -1569,7 +2652,8 @@ function createWorkflowRuntime(registry, shims = void 0) {
1569
2652
  definition,
1570
2653
  exportNames: registry.exportNames,
1571
2654
  request,
1572
- sdkVersion: SDK_VERSION
2655
+ sdkVersion: SDK_VERSION,
2656
+ ...registry.interceptors !== void 0 ? { interceptors: registry.interceptors } : {}
1573
2657
  });
1574
2658
  execution = created;
1575
2659
  shims?.attach({ now: () => created.now(), random: () => created.nextRandom() });