@fluidgrids/workflows 2026.1029.1 → 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 (118) hide show
  1. package/README.md +112 -4
  2. package/dist/{activation-CESehfYi.d.cts → activation-DqUfpHzk.d.cts} +1 -1
  3. package/dist/{activation-BnVQMv9y.d.ts → activation-xpx1oWuo.d.ts} +1 -1
  4. package/dist/{chunk-MJ5TGYVG.js → chunk-2D3MYIIF.js} +969 -153
  5. package/dist/{chunk-2B37XAFQ.cjs → chunk-3U2NU7YC.cjs} +24 -11
  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-2DDULZFM.js → chunk-5HIVIV53.js} +86 -75
  9. package/dist/chunk-5MFVJM76.js +1222 -0
  10. package/dist/{chunk-EWYADREB.cjs → chunk-672KMMBK.cjs} +2 -2
  11. package/dist/{chunk-KXJDRQ6G.js → chunk-6A2FGSUS.js} +22 -2
  12. package/dist/{chunk-OIX4O6UR.js → chunk-6OLN6BMB.js} +16 -3
  13. package/dist/{chunk-6FSHT5IL.js → chunk-7EZIANC7.js} +1 -1
  14. package/dist/chunk-A5SXCA62.cjs +93 -0
  15. package/dist/chunk-AC6ZEXVR.js +85 -0
  16. package/dist/{chunk-FWBRQWRX.js → chunk-AE5Q6ZAJ.js} +167 -10
  17. package/dist/{chunk-7ILGCUI6.js → chunk-C4F5KJTI.js} +142 -3
  18. package/dist/{chunk-WXXNCHUY.js → chunk-G5H32GIG.js} +1 -1
  19. package/dist/{chunk-OMY3CKUZ.cjs → chunk-GEQDGUGB.cjs} +1042 -226
  20. package/dist/{chunk-25BKWR2O.js → chunk-HCM7ZC7Q.js} +1 -1
  21. package/dist/{chunk-I47EENR4.cjs → chunk-HMZUGSMU.cjs} +673 -156
  22. package/dist/{chunk-NXFBVDOI.cjs → chunk-IQPXHQQQ.cjs} +8 -4
  23. package/dist/{chunk-RUQIMTN5.cjs → chunk-ISOIA7NV.cjs} +178 -15
  24. package/dist/chunk-JSC75O5C.cjs +167 -0
  25. package/dist/{chunk-BD7ZPG53.js → chunk-NWQ5WRLE.js} +10 -2
  26. package/dist/chunk-ODZ2IAR2.cjs +1247 -0
  27. package/dist/{chunk-FQSJI5NV.js → chunk-SR7TR5KX.js} +6 -2
  28. package/dist/{chunk-OKX3FYIM.cjs → chunk-TCTE7DX5.cjs} +3 -3
  29. package/dist/{chunk-3FIDHONL.cjs → chunk-TYF56RGB.cjs} +99 -80
  30. package/dist/{chunk-J3PYFGKA.cjs → chunk-VA4OFR72.cjs} +162 -4
  31. package/dist/chunk-VU5S5A7V.js +150 -0
  32. package/dist/{chunk-A36CCOWS.js → chunk-YEHR4NNE.js} +633 -128
  33. package/dist/{chunk-ELL2RBML.cjs → chunk-ZCGKGDCL.cjs} +23 -1
  34. package/dist/codegen/index.d.cts +2 -2
  35. package/dist/codegen/index.d.ts +2 -2
  36. package/dist/{collect-BTHjQaSp.d.ts → collect-6NIEufHd.d.ts} +20 -3
  37. package/dist/{collect-CSyOg5XC.d.cts → collect-CcTCKqx6.d.cts} +20 -3
  38. package/dist/{file-cgL9l9l8.d.cts → file-BxYpwjRQ.d.cts} +250 -15
  39. package/dist/{file-olQudcx5.d.ts → file-CFdPQJI9.d.ts} +250 -15
  40. package/dist/history/index.cjs +283 -106
  41. package/dist/history/index.d.cts +549 -10
  42. package/dist/history/index.d.ts +549 -10
  43. package/dist/history/index.js +5 -4
  44. package/dist/index.cjs +146 -53
  45. package/dist/index.d.cts +38 -14
  46. package/dist/index.d.ts +38 -14
  47. package/dist/index.js +6 -5
  48. package/dist/interceptors-CNcr2dlk.d.ts +159 -0
  49. package/dist/interceptors-CqkNZVfI.d.cts +159 -0
  50. package/dist/ir/index.cjs +339 -36
  51. package/dist/ir/index.d.cts +244 -8
  52. package/dist/ir/index.d.ts +244 -8
  53. package/dist/ir/index.js +306 -21
  54. package/dist/{lower-3S6PsqjJ.d.cts → lower-BtRfIW2A.d.cts} +23 -9
  55. package/dist/{lower-DmZpQ8hJ.d.ts → lower-CpsMFOFX.d.ts} +23 -9
  56. package/dist/{manifest-BJjrQt77.d.ts → manifest-D-I_twys.d.ts} +1 -1
  57. package/dist/{manifest-gRI2Q5Fj.d.cts → manifest-DahBQVM8.d.cts} +1 -1
  58. package/dist/node-evaluator-DGQDPSPI.cjs +24 -0
  59. package/dist/node-evaluator-LEEVD7H7.js +14 -0
  60. package/dist/payload-fields-BVRQn2yH.d.cts +122 -0
  61. package/dist/payload-fields-oOBWxDiG.d.ts +122 -0
  62. package/dist/quickjs/index.cjs +27 -27
  63. package/dist/quickjs/index.d.cts +6 -6
  64. package/dist/quickjs/index.d.ts +6 -6
  65. package/dist/quickjs/index.js +9 -9
  66. package/dist/roundtrip/index.cjs +72 -66
  67. package/dist/roundtrip/index.d.cts +25 -11
  68. package/dist/roundtrip/index.d.ts +25 -11
  69. package/dist/roundtrip/index.js +13 -11
  70. package/dist/runtime/index.cjs +19 -19
  71. package/dist/runtime/index.d.cts +139 -12
  72. package/dist/runtime/index.d.ts +139 -12
  73. package/dist/runtime/index.js +6 -6
  74. package/dist/testing/index.cjs +688 -121
  75. package/dist/testing/index.d.cts +174 -10
  76. package/dist/testing/index.d.ts +174 -10
  77. package/dist/testing/index.js +646 -82
  78. package/dist/{types-CnIOinoz.d.ts → types-Bv2i4cBX.d.cts} +126 -23
  79. package/dist/{types-CQKOhPTa.d.cts → types-DGx-b9eq.d.ts} +126 -23
  80. package/dist/{types-Bb2wRnJ5.d.cts → types-V7jEEUaL.d.cts} +1 -1
  81. package/dist/{types-Bb2wRnJ5.d.ts → types-V7jEEUaL.d.ts} +1 -1
  82. package/dist/{workflow-8uRU9rhL.d.ts → workflow-B18OeF7g.d.ts} +2 -2
  83. package/dist/{workflow-DWTPHFbt.d.ts → workflow-BGnv8SS6.d.ts} +261 -14
  84. package/dist/{workflow-CZOXUlQ5.d.cts → workflow-BJPFv3cp.d.cts} +2 -2
  85. package/dist/{workflow-O6e64ZcG.d.cts → workflow-DS32Gtws.d.cts} +261 -14
  86. package/fixtures/histories/activity-options-updated.history.json +159 -0
  87. package/fixtures/histories/activity-paused-unpaused.history.json +160 -0
  88. package/fixtures/histories/codec-opaque.history.json +229 -0
  89. package/fixtures/histories/dynamic-workflow.history.json +150 -0
  90. package/fixtures/histories/interceptor-headers.history.json +339 -0
  91. package/fixtures/histories/query-custom.history.json +213 -0
  92. package/fixtures/histories/query-default-handler.history.json +120 -0
  93. package/fixtures/histories/reset-forked-reapplied-signals.history.json +239 -0
  94. package/fixtures/histories/reset-reapplied-update-admitted.history.json +228 -0
  95. package/fixtures/histories/search-attributes-upserted.history.json +177 -0
  96. package/fixtures/histories/update-accepted-completed.history.json +45 -30
  97. package/fixtures/histories/update-default-handler.history.json +246 -0
  98. package/fixtures/histories/update-handler-awaits-activity.history.json +196 -0
  99. package/fixtures/histories/update-not-handled.history.json +138 -0
  100. package/fixtures/histories/update-rejected-no-event.history.json +190 -0
  101. package/fixtures/histories/update-with-start-admitted.history.json +150 -0
  102. package/fixtures/histories/user-activity-cancel.history.json +149 -0
  103. package/fixtures/histories/user-activity-retry.history.json +143 -0
  104. package/fixtures/histories/user-activity.history.json +149 -0
  105. package/package.json +1 -1
  106. package/schema/bridge-v2.schema.json +1062 -0
  107. package/schema/history-file.schema.json +255 -81
  108. package/schema/ir-v2.schema.json +246 -1
  109. package/schema/payload-fields.json +162 -0
  110. package/schema/run-event.schema.json +239 -80
  111. package/schema/workflow-command.schema.json +210 -84
  112. package/schema/workflow-task.schema.json +532 -167
  113. package/dist/chunk-6QPOJZT6.cjs +0 -1180
  114. package/dist/chunk-A2OHX3UQ.js +0 -1156
  115. package/dist/chunk-PWIUEWC3.js +0 -81
  116. package/dist/chunk-YHBVMOMK.cjs +0 -87
  117. package/dist/node-evaluator-SOADN23V.js +0 -14
  118. package/dist/node-evaluator-YGDEXK4U.cjs +0 -24
@@ -1,10 +1,10 @@
1
- import { ServerFailure, failureToError, CancelledFailure, TimeoutFailure, ApplicationError, TerminatedFailure, DeterminismViolation, HistoryLimitExceeded, PhaseCFeatureError, isCancellation, isRunFailure, errorToFailure, ActivityFailure, signalName, ChildRunFailure } from './chunk-KXJDRQ6G.js';
1
+ import { QueryHandlerSideEffect, ServerFailure, failureToError, CancelledFailure, TimeoutFailure, ApplicationError, TerminatedFailure, errorToFailure, isRunFailure, DeterminismViolation, HistoryLimitExceeded, PhaseCFeatureError, isCancellation, ActivityFailure, signalName, ChildRunFailure } from './chunk-6A2FGSUS.js';
2
2
  import { captureStack, parseStack } from './chunk-M272AHEX.js';
3
3
  import { createRandom, seedOf, takeShimControl, uuidFrom } from './chunk-4LYSEANA.js';
4
- import { optionalMs, toMs, isDurableWorkflow, runRetryPolicyOf, continueAsNewRetryOf, SDK_VERSION } from './chunk-7ILGCUI6.js';
5
- import { utf8Bytes, splitActivations, CHILD_DEFAULT_CANCELLATION_TYPE, CHILD_DEFAULT_PARENT_CLOSE_POLICY } from './chunk-FWBRQWRX.js';
6
- import { COMMAND_EVENT_TYPES, DEFAULT_HISTORY_LIMIT, CONTINUE_AS_NEW_SUGGESTED_RATIO, HISTORY_PROTOCOL, HISTORY_LIMIT_EXCEEDED_RATIO, isActivityNodeTypeAllowed, LOCAL_ACTIVITY_MAX_TIMEOUT_MS, BUILT_IN_QUERIES } from './chunk-2DDULZFM.js';
7
- import { explicitRetryPolicy, computeRetryDelay } from './chunk-PWIUEWC3.js';
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';
8
8
 
9
9
  // src/runtime/ids.ts
10
10
  var CommandIds = class {
@@ -37,7 +37,7 @@ function field(event, key) {
37
37
  function describeCommand(command) {
38
38
  switch (command.type) {
39
39
  case "SCHEDULE_ACTIVITY":
40
- 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})`;
41
41
  case "REQUEST_CANCEL_ACTIVITY":
42
42
  return `REQUEST_CANCEL_ACTIVITY ${command.activityId}`;
43
43
  case "START_TIMER":
@@ -55,12 +55,16 @@ function describeCommand(command) {
55
55
  return `RECORD_MARKER ${command.markerId}`;
56
56
  case "RECORD_LOCAL_ACTIVITY":
57
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}`;
58
62
  default:
59
63
  return command.type;
60
64
  }
61
65
  }
62
66
  function describeEvent(event) {
63
- 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");
64
68
  return `${event.type}${typeof id === "string" ? ` ${id}` : ""} (seq ${event.seq})`;
65
69
  }
66
70
  function mismatch(command, event) {
@@ -69,8 +73,13 @@ function mismatch(command, event) {
69
73
  const differ = () => `the code issued ${describeCommand(command)} where the history has ${describeEvent(event)}`;
70
74
  if (!typeOk) return differ();
71
75
  switch (command.type) {
72
- case "SCHEDULE_ACTIVITY":
73
- 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
+ }
74
83
  case "REQUEST_CANCEL_ACTIVITY":
75
84
  return field(event, "activityId") === command.activityId ? void 0 : differ();
76
85
  case "START_TIMER":
@@ -86,6 +95,9 @@ function mismatch(command, event) {
86
95
  return field(event, "sideEffectId") === command.sideEffectId ? void 0 : differ();
87
96
  case "RECORD_MARKER":
88
97
  return field(event, "markerId") === command.markerId ? void 0 : differ();
98
+ case "ACCEPT_UPDATE":
99
+ case "COMPLETE_UPDATE":
100
+ return field(event, "updateId") === command.updateId ? void 0 : differ();
89
101
  case "RECORD_LOCAL_ACTIVITY": {
90
102
  const sameOperation = field(event, "nodeType") === void 0 || field(event, "nodeType") === command.nodeType && field(event, "operation") === command.operation;
91
103
  return field(event, "localActivityId") === command.localActivityId && sameOperation ? void 0 : differ();
@@ -101,6 +113,7 @@ function isSkippablePatch(event) {
101
113
  function matchActivation(commands, recorded) {
102
114
  let position = 0;
103
115
  for (const command of commands) {
116
+ if (COMMAND_EVENT_TYPES[command.type] === null) continue;
104
117
  let event = recorded[position];
105
118
  while (event !== void 0 && isSkippablePatch(event) && !(command.type === "RECORD_MARKER" && command.markerId === field(event, "markerId"))) {
106
119
  position += 1;
@@ -126,15 +139,35 @@ function matchActivation(commands, recorded) {
126
139
  }
127
140
 
128
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
+ }
129
154
  var SDK_NAME = "@fluidgrids/workflows";
130
155
  var ContinueAsNewRequest = class {
131
- constructor(input, options) {
156
+ constructor(input, options, headers = {}) {
132
157
  this.input = input;
133
158
  this.options = options;
159
+ this.headers = headers;
134
160
  }
135
161
  input;
136
162
  options;
163
+ headers;
137
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
+ }
138
171
  var TaskFailure = class {
139
172
  constructor(cause, message, stackTrace, seq, errorType) {
140
173
  this.cause = cause;
@@ -209,6 +242,32 @@ var Scope = class {
209
242
  this.parent?.children.delete(this);
210
243
  }
211
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
+ }
212
271
  function toJson(value, what) {
213
272
  if (value === void 0) return null;
214
273
  if (typeof value === "function" || typeof value === "symbol")
@@ -277,6 +336,11 @@ function infoFromStarted(info, started) {
277
336
  if (typeof p["priority"] === "number") out.priority = p["priority"];
278
337
  if (typeof p["firstRunId"] === "string") out.firstRunId = p["firstRunId"];
279
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"];
280
344
  if (out.workflowId === void 0 && out.businessId !== void 0) out.workflowId = out.businessId;
281
345
  if (out.runAttempt === void 0 && out.attempt !== void 0) out.runAttempt = out.attempt;
282
346
  return out;
@@ -291,7 +355,7 @@ var WorkflowExecution = class {
291
355
  activationOpen = false;
292
356
  replaying = false;
293
357
  clock = 0;
294
- random;
358
+ randomSource;
295
359
  ids = new CommandIds();
296
360
  commands = [];
297
361
  newCommands = [];
@@ -336,6 +400,24 @@ var WorkflowExecution = class {
336
400
  /** Running asynchronous handlers by name, for the drain warning (11 §2.18). */
337
401
  running = /* @__PURE__ */ new Map();
338
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;
339
421
  recordedLocalActivities = /* @__PURE__ */ new Map();
340
422
  localActivityResults = /* @__PURE__ */ new Map();
341
423
  localActivities = /* @__PURE__ */ new Map();
@@ -362,7 +444,7 @@ var WorkflowExecution = class {
362
444
  this.limitEvents = limit.events;
363
445
  this.limitBytes = limit.bytes;
364
446
  this.captureStacks = input.request.kind === "query";
365
- this.random = createRandom(seedOf(randomnessSeedOf(input.request)));
447
+ this.randomSource = createRandom(seedOf(randomnessSeedOf(input.request)));
366
448
  for (const event of history) {
367
449
  if (event.type === "SIDE_EFFECT_RECORDED") {
368
450
  const id = event.payload["sideEffectId"];
@@ -396,6 +478,30 @@ var WorkflowExecution = class {
396
478
  const execution = this;
397
479
  const snapshot = infoFromStarted(input.request.info, history[0]);
398
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;
399
505
  this.info = {
400
506
  ...snapshot,
401
507
  workflowFn: input.request.workflowFn,
@@ -405,6 +511,9 @@ var WorkflowExecution = class {
405
511
  get memo() {
406
512
  return { ...execution.memo };
407
513
  },
514
+ get searchAttributes() {
515
+ return { ...execution.searchAttributes };
516
+ },
408
517
  get historyLength() {
409
518
  return execution.historyLength;
410
519
  },
@@ -427,9 +536,33 @@ var WorkflowExecution = class {
427
536
  now() {
428
537
  return this.clock;
429
538
  }
430
- /** The next deterministic random number. */
539
+ /** The next deterministic random number (`Math.random()` and `crypto.getRandomValues()`). */
431
540
  nextRandom() {
432
- 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);
433
566
  }
434
567
  /** Begin or finish an activation; `done` when there is nothing left to do. */
435
568
  step() {
@@ -469,6 +602,8 @@ var WorkflowExecution = class {
469
602
  logs: this.logs
470
603
  };
471
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;
472
607
  if (this.failure) {
473
608
  result.failure = { cause: this.failure.cause, message: this.failure.message };
474
609
  if (this.failure.stackTrace !== void 0)
@@ -476,7 +611,8 @@ var WorkflowExecution = class {
476
611
  if (this.failure.seq !== void 0) result.failure.seq = this.failure.seq;
477
612
  if (this.failure.errorType !== void 0) result.failure.errorType = this.failure.errorType;
478
613
  }
479
- 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();
480
616
  return result;
481
617
  }
482
618
  /**
@@ -503,11 +639,54 @@ var WorkflowExecution = class {
503
639
  if (this.failure) return;
504
640
  this.deliver(event);
505
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;
506
684
  }
507
685
  finishActivation() {
508
686
  this.activationOpen = false;
509
687
  const activation = this.activations[this.index];
510
688
  if (!activation) return;
689
+ this.rejectUnhandledUpdates();
511
690
  if (activation.recorded) {
512
691
  const found = matchActivation(this.commands, activation.recorded);
513
692
  if (found)
@@ -567,8 +746,20 @@ var WorkflowExecution = class {
567
746
  return;
568
747
  }
569
748
  case "SIGNAL_RECEIVED":
570
- this.dispatchSignal(id("name"), p["payload"] ?? null);
749
+ this.dispatchSignal(id("name"), p["payload"] ?? null, headersOf(p["headers"]));
571
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
+ }
572
763
  case "CHILD_RUN_STARTED":
573
764
  case "CHILD_RUN_COMPLETED":
574
765
  case "CHILD_RUN_FAILED":
@@ -618,12 +809,14 @@ var WorkflowExecution = class {
618
809
  pending.release?.();
619
810
  const p = event.payload;
620
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}`;
621
813
  const wrap = (cause) => new ActivityFailure(
622
- `Activity ${activityId} (${pending.nodeType} ${pending.operation}) failed: ${cause.message}`,
814
+ `Activity ${activityId} (${label}) failed: ${cause.message}`,
623
815
  activityId,
624
816
  pending.nodeType,
625
817
  pending.operation,
626
- cause
818
+ cause,
819
+ pending.activityName
627
820
  );
628
821
  switch (event.type) {
629
822
  case "ACTIVITY_COMPLETED":
@@ -715,10 +908,24 @@ var WorkflowExecution = class {
715
908
  else result.reject(outcome.error);
716
909
  }
717
910
  }
718
- 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 = {}) {
719
922
  const handler = this.signalHandlers.get(name);
720
923
  if (handler) {
721
- this.runHandler(() => handler(payload), name, this.signalPolicies.get(name));
924
+ this.runHandler(
925
+ this.signalCall(name, payload, headers, handler),
926
+ name,
927
+ this.signalPolicies.get(name)
928
+ );
722
929
  return;
723
930
  }
724
931
  const waiters = this.signalWaiters.get(name);
@@ -730,7 +937,11 @@ var WorkflowExecution = class {
730
937
  }
731
938
  if (this.defaultSignalHandler) {
732
939
  const fallback = this.defaultSignalHandler;
733
- this.runHandler(() => fallback(name, payload), name, this.defaultSignalPolicy);
940
+ this.runHandler(
941
+ this.signalCall(name, payload, headers, (value) => fallback(name, value)),
942
+ name,
943
+ this.defaultSignalPolicy
944
+ );
734
945
  return;
735
946
  }
736
947
  const buffer = this.signalBuffers.get(name) ?? [];
@@ -755,22 +966,228 @@ var WorkflowExecution = class {
755
966
  return;
756
967
  }
757
968
  if (result instanceof Promise) {
758
- this.runningHandlers += 1;
759
- const entry = this.running.get(name) ?? { count: 0, policy };
760
- entry.count += 1;
761
- entry.policy = policy;
762
- this.running.set(name, entry);
763
- const done = () => {
764
- this.runningHandlers -= 1;
765
- entry.count -= 1;
766
- if (entry.count === 0) this.running.delete(name);
767
- };
969
+ const done = this.trackHandler("signal", name, policy);
768
970
  result.then(done, (error) => {
769
971
  done();
770
972
  this.onWorkflowError(error);
771
973
  });
772
974
  }
773
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);
1090
+ result.then(
1091
+ (value) => {
1092
+ done();
1093
+ this.completeUpdate(update, { ok: true, value });
1094
+ },
1095
+ (error) => {
1096
+ done();
1097
+ this.completeUpdate(update, { ok: false, error });
1098
+ }
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;
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
+ }
774
1191
  /**
775
1192
  * The drain check of 11 §2.18 when the workflow finishes: handlers still
776
1193
  * running with policy `WARN_AND_ABANDON` give one warning line and
@@ -778,13 +1195,18 @@ var WorkflowExecution = class {
778
1195
  */
779
1196
  checkUnfinishedHandlers(command) {
780
1197
  if (this.replaying || command.type === "CANCEL_WORKFLOW") return;
781
- const unfinished = [...this.running.entries()].filter(([, entry]) => entry.count > 0 && entry.policy === "WARN_AND_ABANDON").map(([name, entry]) => ({ kind: "signal", name, count: entry.count })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
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
+ );
782
1203
  if (unfinished.length === 0) return;
783
1204
  this.unfinished = unfinished;
784
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";
785
1207
  this.logs.push({
786
1208
  level: "warn",
787
- message: `The workflow finished while ${total} signal handler${total === 1 ? " was" : "s were"} still running (${unfinished.map((entry) => `${entry.name} x${entry.count}`).join(
1209
+ message: `The workflow finished while ${total} ${what}${total === 1 ? " was" : "s were"} still running (${unfinished.map((entry) => `${entry.name} x${entry.count}`).join(
788
1210
  ", "
789
1211
  )}); await ctx.allHandlersFinished() before returning, or set unfinishedPolicy: 'ABANDON' on the handler`,
790
1212
  data: unfinished.map((entry) => ({ kind: entry.kind, name: entry.name, count: entry.count }))
@@ -794,7 +1216,15 @@ var WorkflowExecution = class {
794
1216
  startFunction(input) {
795
1217
  let promise;
796
1218
  try {
797
- 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 } });
798
1228
  } catch (error) {
799
1229
  promise = Promise.reject(error);
800
1230
  }
@@ -824,7 +1254,13 @@ var WorkflowExecution = class {
824
1254
  onWorkflowError(error) {
825
1255
  if (this.closed || this.failure) return;
826
1256
  if (error instanceof ContinueAsNewRequest) {
827
- 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);
828
1264
  return;
829
1265
  }
830
1266
  const name = errorName(error);
@@ -844,6 +1280,10 @@ var WorkflowExecution = class {
844
1280
  this.failTask("UNSUPPORTED_COMMAND", messageOf(error), stackTextOf(error));
845
1281
  return;
846
1282
  }
1283
+ if (error instanceof QueryHandlerSideEffect) {
1284
+ this.failTask("WORKFLOW_CODE_ERROR", messageOf(error), stackTextOf(error), void 0, name);
1285
+ return;
1286
+ }
847
1287
  if (this.cancelRequested && isCancellation(error)) {
848
1288
  this.pushTerminal({ type: "CANCEL_WORKFLOW" });
849
1289
  return;
@@ -878,6 +1318,7 @@ var WorkflowExecution = class {
878
1318
  }
879
1319
  /** Issue a command; false when the run is already closed (the command is dropped). */
880
1320
  push(command) {
1321
+ this.assertWritable(command.type);
881
1322
  if (this.closed || this.failure) return false;
882
1323
  this.checkLimit();
883
1324
  this.commands.push(command);
@@ -903,7 +1344,7 @@ var WorkflowExecution = class {
903
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`,
904
1345
  { type: "durable.activityTypeNotAllowed", errorType: "VALIDATION", nonRetryable: true }
905
1346
  );
906
- const activityId = this.ids.next("a");
1347
+ const activityId = this.nextId("a");
907
1348
  const operationName = options.operation ?? operation.operation ?? "execute";
908
1349
  const nodeVersion = options.nodeVersion ?? operation.version;
909
1350
  const cancellationType = options.cancellationType ?? "WAIT_CANCELLATION_COMPLETED";
@@ -930,20 +1371,119 @@ var WorkflowExecution = class {
930
1371
  if (options.priority !== void 0) command.priority = options.priority;
931
1372
  if (options.summary !== void 0) command.summary = options.summary;
932
1373
  if (options.runOn !== void 0) command.runOn = options.runOn;
933
- 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;
934
1481
  if (scope.consideredCancelled)
935
1482
  return Promise.reject(new CancelledFailure("Scope is cancelled"));
936
1483
  if (!this.push(command)) return this.never();
937
1484
  this.issued.add(`activity:${activityId}`);
938
1485
  return new Promise((resolve, reject) => {
939
- const entry = {
940
- resolve,
941
- reject,
942
- description: `activity ${activityId} (${operation.nodeType} ${operationName})`,
943
- nodeType: operation.nodeType,
944
- operation: operationName,
945
- cancellationType
946
- };
1486
+ const entry = { ...pending, resolve, reject };
947
1487
  const stack = this.stack();
948
1488
  if (stack !== void 0) entry.stack = stack;
949
1489
  entry.release = scope.onCancel(() => {
@@ -953,7 +1493,8 @@ var WorkflowExecution = class {
953
1493
  activityId,
954
1494
  entry.nodeType,
955
1495
  entry.operation,
956
- new CancelledFailure("Activity cancelled")
1496
+ new CancelledFailure("Activity cancelled"),
1497
+ entry.activityName
957
1498
  );
958
1499
  if (cancellationType !== "ABANDON")
959
1500
  this.push({ type: "REQUEST_CANCEL_ACTIVITY", activityId });
@@ -993,80 +1534,94 @@ var WorkflowExecution = class {
993
1534
  throw new TypeError(
994
1535
  `A local activity's startToCloseTimeout must be between 1 ms and 30 s (got ${timeout} ms); use ctx.run for longer calls`
995
1536
  );
996
- const localActivityId = this.ids.next("l");
1537
+ const localActivityId = this.nextId("l");
997
1538
  const operationName = options.operation ?? descriptor.operation ?? "execute";
998
1539
  const nodeVersion = options.nodeVersion ?? descriptor.version;
999
1540
  const retry = explicitRetryPolicy(options.retry);
1000
1541
  const args = toJson(options.args, `The args of ${descriptor.nodeType}`);
1001
- if (scope.consideredCancelled)
1002
- return Promise.reject(new CancelledFailure("Scope is cancelled"));
1003
- const label = `local activity ${localActivityId} (${descriptor.nodeType} ${operationName})`;
1004
- const wrap = (cause) => new ActivityFailure(
1005
- `Local activity ${localActivityId} (${descriptor.nodeType} ${operationName}) failed: ${cause.message}`,
1006
- localActivityId,
1007
- descriptor.nodeType,
1008
- operationName,
1009
- cause
1010
- );
1011
- const record = (outcome) => {
1012
- const command = {
1013
- type: "RECORD_LOCAL_ACTIVITY",
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}`,
1014
1548
  localActivityId,
1015
- attempts: Math.max(1, Math.round(outcome.attempts)),
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",
1016
1586
  nodeType: descriptor.nodeType,
1017
1587
  operation: operationName,
1018
- ...outcome.failure !== void 0 ? { failure: outcome.failure } : { result: outcome.result ?? null }
1588
+ args: args2,
1589
+ retry,
1590
+ startToCloseTimeout: timeout,
1591
+ attempt,
1592
+ backoffMs
1019
1593
  };
1020
- if (options.summary !== void 0) command.summary = options.summary;
1021
- if (!this.push(command)) return this.never();
1022
- if (outcome.failure !== void 0)
1023
- return Promise.reject(wrap(failureToError(outcome.failure)));
1024
- return Promise.resolve(outcome.result ?? null);
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
+ });
1025
1613
  };
1026
- const replayed = this.replaying;
1027
- if (replayed) {
1028
- const recorded = this.recordedLocalActivities.get(localActivityId);
1029
- if (recorded !== void 0) return record(recorded);
1030
- }
1031
- const given = replayed ? void 0 : this.localActivityResults.get(localActivityId);
1032
- let attempt = 1;
1033
- let backoffMs = 0;
1034
- if (given !== void 0) {
1035
- if (given.failure === void 0) return record(given);
1036
- const decision = computeRetryDelay(retry, given.attempts, given.failure);
1037
- if (!decision.retry) return record(given);
1038
- attempt = given.attempts + 1;
1039
- backoffMs = decision.delayMs;
1040
- }
1041
- const request = {
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({
1042
1621
  localActivityId,
1043
- kind: "node",
1044
1622
  nodeType: descriptor.nodeType,
1045
1623
  operation: operationName,
1046
- args,
1047
- retry,
1048
- startToCloseTimeout: timeout,
1049
- attempt,
1050
- backoffMs
1051
- };
1052
- if (nodeVersion !== void 0) request.nodeVersion = nodeVersion;
1053
- if (options.connection !== void 0) request.connection = options.connection;
1054
- if (options.summary !== void 0) request.summary = options.summary;
1055
- return new Promise((resolve, reject) => {
1056
- const entry = {
1057
- resolve,
1058
- reject,
1059
- description: label,
1060
- request,
1061
- replayed
1062
- };
1063
- const stack = this.stack();
1064
- if (stack !== void 0) entry.stack = stack;
1065
- entry.release = scope.onCancel(() => {
1066
- if (!this.localActivities.delete(localActivityId)) return;
1067
- reject(wrap(new CancelledFailure("Local activity cancelled")));
1068
- });
1069
- this.localActivities.set(localActivityId, entry);
1624
+ args
1070
1625
  });
1071
1626
  }
1072
1627
  startTimer(scope, durationMs, summary) {
@@ -1077,7 +1632,7 @@ var WorkflowExecution = class {
1077
1632
  if (durationMs <= 0) return { promise: Promise.resolve() };
1078
1633
  if (scope.consideredCancelled)
1079
1634
  return { promise: Promise.reject(new CancelledFailure("Scope is cancelled")) };
1080
- const timerId = this.ids.next("t");
1635
+ const timerId = this.nextId("t");
1081
1636
  const command = {
1082
1637
  type: "START_TIMER",
1083
1638
  timerId,
@@ -1192,9 +1747,7 @@ var WorkflowExecution = class {
1192
1747
  }
1193
1748
  onSignal(signal, handler, options = {}) {
1194
1749
  if (typeof handler !== "function") throw new TypeError("ctx.onSignal needs a handler function");
1195
- const policy = options.unfinishedPolicy ?? "WARN_AND_ABANDON";
1196
- if (policy !== "WARN_AND_ABANDON" && policy !== "ABANDON")
1197
- throw new TypeError("unfinishedPolicy must be WARN_AND_ABANDON or ABANDON");
1750
+ const policy = checkPolicy(options.unfinishedPolicy);
1198
1751
  const name = signalName(signal);
1199
1752
  this.signalHandlers.set(name, handler);
1200
1753
  this.signalPolicies.set(name, policy);
@@ -1294,13 +1847,39 @@ var WorkflowExecution = class {
1294
1847
  ...opts
1295
1848
  }
1296
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;
1297
1854
  if (options.version !== void 0) command.version = options.version;
1298
1855
  if (options.headers !== void 0) command.headers = { ...options.headers };
1299
1856
  return command;
1300
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
+ }
1301
1880
  startChildRun(scope, target, options, defaultPolicy) {
1302
1881
  if (scope.consideredCancelled) return void 0;
1303
- const childId = this.ids.next("c");
1882
+ const childId = this.nextId("c");
1304
1883
  const command = this.childCommand(childId, target, options, defaultPolicy);
1305
1884
  if (!this.push(command)) return void 0;
1306
1885
  this.issued.add(`child:${childId}`);
@@ -1325,7 +1904,7 @@ var WorkflowExecution = class {
1325
1904
  entry.reject(error);
1326
1905
  return;
1327
1906
  }
1328
- const externalId = this.ids.next("x");
1907
+ const externalId = this.nextId("x");
1329
1908
  if (this.push({
1330
1909
  type: "REQUEST_CANCEL_EXTERNAL_RUN",
1331
1910
  externalId,
@@ -1359,15 +1938,17 @@ var WorkflowExecution = class {
1359
1938
  };
1360
1939
  }
1361
1940
  child(scope, target, options) {
1362
- const child = this.startChildRun(
1363
- scope,
1364
- target,
1365
- options,
1366
- CHILD_DEFAULT_PARENT_CLOSE_POLICY.child
1367
- );
1368
- if (!child)
1369
- return scope.consideredCancelled ? Promise.reject(new CancelledFailure("Scope is cancelled")) : this.never();
1370
- 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
+ });
1371
1952
  }
1372
1953
  childResult(child) {
1373
1954
  if (child.settled && child.outcome) {
@@ -1387,6 +1968,13 @@ var WorkflowExecution = class {
1387
1968
  });
1388
1969
  }
1389
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) {
1390
1978
  const child = this.startChildRun(
1391
1979
  scope,
1392
1980
  target,
@@ -1416,7 +2004,21 @@ var WorkflowExecution = class {
1416
2004
  });
1417
2005
  }
1418
2006
  signalExternal(target, name, payload) {
1419
- 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");
1420
2022
  const command = {
1421
2023
  type: "SIGNAL_RUN",
1422
2024
  externalId,
@@ -1424,6 +2026,7 @@ var WorkflowExecution = class {
1424
2026
  name,
1425
2027
  payload: toJson(payload, `The payload of signal ${name}`)
1426
2028
  };
2029
+ if (headers !== void 0 && Object.keys(headers).length > 0) command.headers = { ...headers };
1427
2030
  if (!this.push(command)) return this.never();
1428
2031
  this.issued.add(`external:${externalId}`);
1429
2032
  return new Promise((resolve, reject) => {
@@ -1438,7 +2041,15 @@ var WorkflowExecution = class {
1438
2041
  });
1439
2042
  }
1440
2043
  cancelExternal(target) {
1441
- 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");
1442
2053
  if (!this.push({ type: "REQUEST_CANCEL_EXTERNAL_RUN", externalId, target }))
1443
2054
  return this.never();
1444
2055
  this.issued.add(`external:${externalId}`);
@@ -1454,7 +2065,15 @@ var WorkflowExecution = class {
1454
2065
  });
1455
2066
  }
1456
2067
  sideEffect(fn) {
1457
- 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");
1458
2077
  let value;
1459
2078
  if (this.replaying && this.recordedSideEffects.has(sideEffectId)) {
1460
2079
  value = this.recordedSideEffects.get(sideEffectId) ?? null;
@@ -1468,6 +2087,7 @@ var WorkflowExecution = class {
1468
2087
  if (typeof patchId !== "string" || patchId === "") throw new TypeError("A patch needs an id");
1469
2088
  const known = this.patches.get(patchId);
1470
2089
  if (known !== void 0) return known;
2090
+ this.assertWritable(deprecated ? "ctx.deprecatePatch()" : "ctx.patched()");
1471
2091
  const markerId = patchMarkerId(patchId);
1472
2092
  let result;
1473
2093
  if (this.replaying) {
@@ -1494,17 +2114,113 @@ var WorkflowExecution = class {
1494
2114
  }
1495
2115
  setCurrentDetails(markdown) {
1496
2116
  if (typeof markdown !== "string") throw new TypeError("setCurrentDetails takes Markdown text");
2117
+ this.assertWritable("ctx.setCurrentDetails()");
1497
2118
  this.currentDetails = markdown;
1498
2119
  this.push({
1499
2120
  type: "RECORD_MARKER",
1500
- markerId: this.ids.next("m"),
2121
+ markerId: this.nextId("m"),
1501
2122
  data: { currentDetails: markdown }
1502
2123
  });
1503
2124
  }
1504
2125
  upsertContext(entries) {
1505
2126
  const json = toJson(entries, "The context entries");
1506
2127
  if (!isJsonObject(json)) throw new TypeError("ctx.upsertContext takes an object");
1507
- if (this.push({ type: "UPSERT_CONTEXT", entries: json })) this.memo = { ...this.memo, ...json };
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}`))
2181
+ });
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: {} });
1508
2224
  }
1509
2225
  /** The request a `ctx.continueAsNew` call throws; the run ends with `CONTINUE_AS_NEW`. */
1510
2226
  continueAsNew(input, options) {
@@ -1578,13 +2294,49 @@ var WorkflowExecution = class {
1578
2294
  );
1579
2295
  return out;
1580
2296
  }
1581
- answerQueries() {
1582
- return (this.request.queries ?? []).map((query) => {
1583
- 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")
1584
2325
  return {
1585
2326
  queryId: query.queryId,
1586
- 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`
1587
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);
1588
2340
  if (this.failure)
1589
2341
  return { queryId: query.queryId, error: `The replay failed: ${this.failure.message}` };
1590
2342
  if (query.name === "__stack_trace")
@@ -1632,9 +2384,20 @@ ${entry.stack}`).join("\n\n")
1632
2384
  contextFor(bound) {
1633
2385
  const scope = () => bound ?? this.currentScope();
1634
2386
  const log = (level) => (message, data) => {
1635
- if (this.replaying) return;
2387
+ if (this.replaying || this.readOnly !== void 0) return;
1636
2388
  const line = { level, message: String(message) };
1637
- 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
+ }
1638
2401
  this.logs.push(line);
1639
2402
  };
1640
2403
  const logger = {
@@ -1645,14 +2408,21 @@ ${entry.stack}`).join("\n\n")
1645
2408
  };
1646
2409
  const scopeFn = ((fn, options = {}) => this.cancellationScope(scope(), fn, options));
1647
2410
  scopeFn.current = () => scope();
1648
- const phaseC = (feature) => () => {
1649
- throw new PhaseCFeatureError(feature);
1650
- };
1651
2411
  const context = {
1652
2412
  info: this.info,
1653
2413
  log: logger,
1654
2414
  run: (operation, options) => this.scheduleActivity(scope(), operation, options),
1655
- 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
+ },
1656
2426
  condition: (predicate, timeout) => this.condition(scope(), predicate, timeout),
1657
2427
  signalReceived: (signal) => this.signalReceived(scope(), signal),
1658
2428
  onSignal: ((signal, optionsOrHandler, maybeHandler) => {
@@ -1670,7 +2440,10 @@ ${entry.stack}`).join("\n\n")
1670
2440
  cancelRun: (target) => this.cancelExternal(this.externalTarget(target)),
1671
2441
  now: () => this.clock,
1672
2442
  random: () => this.random(),
1673
- uuid: () => uuidFrom(this.random),
2443
+ uuid: () => {
2444
+ this.assertWritable("ctx.uuid()");
2445
+ return uuidFrom(this.randomSource);
2446
+ },
1674
2447
  sideEffect: (fn) => this.sideEffect(fn),
1675
2448
  patched: (patchId) => this.patched(patchId, false),
1676
2449
  deprecatePatch: (patchId) => {
@@ -1678,13 +2451,7 @@ ${entry.stack}`).join("\n\n")
1678
2451
  },
1679
2452
  setCurrentDetails: (markdown) => this.setCurrentDetails(markdown),
1680
2453
  upsertContext: (entries) => this.upsertContext(entries),
1681
- continueAsNew: (input, options = {}) => {
1682
- try {
1683
- return Promise.reject(this.continueAsNew(input, options));
1684
- } catch (error) {
1685
- return Promise.reject(error);
1686
- }
1687
- },
2454
+ continueAsNew: (input, options = {}) => this.continueAsNewCall(input, options),
1688
2455
  cancellationScope: scopeFn,
1689
2456
  nonCancellable: (fn) => this.cancellationScope(scope(), fn, { nonCancellable: true }),
1690
2457
  onCancel: (fn) => {
@@ -1720,11 +2487,14 @@ ${entry.stack}`).join("\n\n")
1720
2487
  };
1721
2488
  },
1722
2489
  allHandlersFinished: () => this.allHandlersFinished(),
1723
- activity: phaseC("ctx.activity (user activities)"),
2490
+ activity: (activity, options) => this.userActivity(scope(), activity, options),
1724
2491
  localActivity: ((operation, options) => this.localActivity(scope(), operation, options)),
1725
- handleQuery: phaseC("ctx.handleQuery (custom queries)"),
1726
- handleUpdate: phaseC("ctx.handleUpdate (updates)"),
1727
- upsertSearchAttributes: phaseC("ctx.upsertSearchAttributes (search attributes)")
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()
1728
2498
  };
1729
2499
  return Object.freeze(context);
1730
2500
  }
@@ -1736,17 +2506,36 @@ function buildWorkflowRegistry(modules) {
1736
2506
  const byName = /* @__PURE__ */ new Map();
1737
2507
  const exportNames = /* @__PURE__ */ new Map();
1738
2508
  const ambiguous = /* @__PURE__ */ new Set();
2509
+ const interceptors = [];
2510
+ let dynamic;
2511
+ let dynamicAmbiguous = false;
1739
2512
  for (const [, namespace] of modules) {
1740
2513
  for (const exportName of Object.keys(namespace).sort()) {
1741
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
+ }
1742
2520
  if (!isDurableWorkflow(value)) continue;
1743
2521
  const existing = byName.get(exportName);
1744
2522
  if (existing && existing !== value) ambiguous.add(exportName);
1745
2523
  byName.set(exportName, value);
1746
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
+ }
1747
2529
  }
1748
2530
  }
1749
- 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
+ };
1750
2539
  }
1751
2540
  function failedResult(cause, message) {
1752
2541
  return {
@@ -1775,6 +2564,23 @@ function checkRequest(value) {
1775
2564
  }
1776
2565
  if (typeof value["nowMs"] !== "number" || !Number.isFinite(value["nowMs"]))
1777
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";
1778
2584
  return value;
1779
2585
  }
1780
2586
  function createWorkflowRuntime(registry, shims = void 0) {
@@ -1791,6 +2597,7 @@ function createWorkflowRuntime(registry, shims = void 0) {
1791
2597
  return Object.freeze({
1792
2598
  protocol: HISTORY_PROTOCOL,
1793
2599
  sdkVersion: SDK_VERSION,
2600
+ capabilities: Object.freeze([...RUNTIME_CAPABILITIES]),
1794
2601
  start(requestJson) {
1795
2602
  shims?.detach();
1796
2603
  execution = void 0;
@@ -1824,7 +2631,15 @@ function createWorkflowRuntime(registry, shims = void 0) {
1824
2631
  );
1825
2632
  return;
1826
2633
  }
1827
- 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;
1828
2643
  if (!definition) {
1829
2644
  early = failedResult(
1830
2645
  "INVALID_REQUEST",
@@ -1837,7 +2652,8 @@ function createWorkflowRuntime(registry, shims = void 0) {
1837
2652
  definition,
1838
2653
  exportNames: registry.exportNames,
1839
2654
  request,
1840
- sdkVersion: SDK_VERSION
2655
+ sdkVersion: SDK_VERSION,
2656
+ ...registry.interceptors !== void 0 ? { interceptors: registry.interceptors } : {}
1841
2657
  });
1842
2658
  execution = created;
1843
2659
  shims?.attach({ now: () => created.now(), random: () => created.nextRandom() });