@proompteng/temporal-bun-sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +141 -10
  2. package/dist/src/bin/replay-command.d.ts.map +1 -1
  3. package/dist/src/bin/replay-command.js +6 -2
  4. package/dist/src/bin/replay-command.js.map +1 -1
  5. package/dist/src/bin/temporal-bun.d.ts +1 -1
  6. package/dist/src/bin/temporal-bun.d.ts.map +1 -1
  7. package/dist/src/bin/temporal-bun.js +74 -0
  8. package/dist/src/bin/temporal-bun.js.map +1 -1
  9. package/dist/src/client/layer.d.ts +2 -2
  10. package/dist/src/client/layer.d.ts.map +1 -1
  11. package/dist/src/client/retries.d.ts.map +1 -1
  12. package/dist/src/client/retries.js +27 -3
  13. package/dist/src/client/retries.js.map +1 -1
  14. package/dist/src/client/serialization.d.ts +34 -2
  15. package/dist/src/client/serialization.d.ts.map +1 -1
  16. package/dist/src/client/serialization.js +78 -5
  17. package/dist/src/client/serialization.js.map +1 -1
  18. package/dist/src/client/types.d.ts +26 -0
  19. package/dist/src/client/types.d.ts.map +1 -1
  20. package/dist/src/client.d.ts +22 -6
  21. package/dist/src/client.d.ts.map +1 -1
  22. package/dist/src/client.js +488 -39
  23. package/dist/src/client.js.map +1 -1
  24. package/dist/src/common/payloads/codecs.d.ts +38 -0
  25. package/dist/src/common/payloads/codecs.d.ts.map +1 -0
  26. package/dist/src/common/payloads/codecs.js +174 -0
  27. package/dist/src/common/payloads/codecs.js.map +1 -0
  28. package/dist/src/common/payloads/converter.d.ts +52 -3
  29. package/dist/src/common/payloads/converter.d.ts.map +1 -1
  30. package/dist/src/common/payloads/converter.js +340 -2
  31. package/dist/src/common/payloads/converter.js.map +1 -1
  32. package/dist/src/common/payloads/failure.d.ts +5 -6
  33. package/dist/src/common/payloads/failure.d.ts.map +1 -1
  34. package/dist/src/common/payloads/failure.js +3 -52
  35. package/dist/src/common/payloads/failure.js.map +1 -1
  36. package/dist/src/common/payloads/index.d.ts +1 -0
  37. package/dist/src/common/payloads/index.d.ts.map +1 -1
  38. package/dist/src/common/payloads/index.js +1 -0
  39. package/dist/src/common/payloads/index.js.map +1 -1
  40. package/dist/src/config.d.ts +9 -0
  41. package/dist/src/config.d.ts.map +1 -1
  42. package/dist/src/config.js +62 -1
  43. package/dist/src/config.js.map +1 -1
  44. package/dist/src/index.d.ts +1 -1
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js.map +1 -1
  47. package/dist/src/interceptors/client.d.ts +28 -0
  48. package/dist/src/interceptors/client.d.ts.map +1 -0
  49. package/dist/src/interceptors/client.js +169 -0
  50. package/dist/src/interceptors/client.js.map +1 -0
  51. package/dist/src/interceptors/types.d.ts +33 -0
  52. package/dist/src/interceptors/types.d.ts.map +1 -0
  53. package/dist/src/interceptors/types.js +44 -0
  54. package/dist/src/interceptors/types.js.map +1 -0
  55. package/dist/src/interceptors/worker.d.ts +22 -0
  56. package/dist/src/interceptors/worker.d.ts.map +1 -0
  57. package/dist/src/interceptors/worker.js +156 -0
  58. package/dist/src/interceptors/worker.js.map +1 -0
  59. package/dist/src/runtime/cli-layer.d.ts +4 -3
  60. package/dist/src/runtime/cli-layer.d.ts.map +1 -1
  61. package/dist/src/runtime/cli-layer.js +5 -2
  62. package/dist/src/runtime/cli-layer.js.map +1 -1
  63. package/dist/src/runtime/effect-layers.d.ts +9 -0
  64. package/dist/src/runtime/effect-layers.d.ts.map +1 -1
  65. package/dist/src/runtime/effect-layers.js +15 -0
  66. package/dist/src/runtime/effect-layers.js.map +1 -1
  67. package/dist/src/worker/concurrency.d.ts +8 -0
  68. package/dist/src/worker/concurrency.d.ts.map +1 -1
  69. package/dist/src/worker/concurrency.js +5 -9
  70. package/dist/src/worker/concurrency.js.map +1 -1
  71. package/dist/src/worker/runtime.d.ts +5 -0
  72. package/dist/src/worker/runtime.d.ts.map +1 -1
  73. package/dist/src/worker/runtime.js +509 -40
  74. package/dist/src/worker/runtime.js.map +1 -1
  75. package/dist/src/worker/sticky-cache.d.ts +5 -0
  76. package/dist/src/worker/sticky-cache.d.ts.map +1 -1
  77. package/dist/src/worker/sticky-cache.js +26 -0
  78. package/dist/src/worker/sticky-cache.js.map +1 -1
  79. package/dist/src/worker/update-protocol.d.ts +33 -0
  80. package/dist/src/worker/update-protocol.d.ts.map +1 -0
  81. package/dist/src/worker/update-protocol.js +243 -0
  82. package/dist/src/worker/update-protocol.js.map +1 -0
  83. package/dist/src/worker.js +1 -0
  84. package/dist/src/worker.js.map +1 -1
  85. package/dist/src/workflow/commands.d.ts +38 -2
  86. package/dist/src/workflow/commands.d.ts.map +1 -1
  87. package/dist/src/workflow/commands.js +153 -1
  88. package/dist/src/workflow/commands.js.map +1 -1
  89. package/dist/src/workflow/context.d.ts +111 -3
  90. package/dist/src/workflow/context.d.ts.map +1 -1
  91. package/dist/src/workflow/context.js +526 -6
  92. package/dist/src/workflow/context.js.map +1 -1
  93. package/dist/src/workflow/definition.d.ts +29 -3
  94. package/dist/src/workflow/definition.d.ts.map +1 -1
  95. package/dist/src/workflow/definition.js +30 -4
  96. package/dist/src/workflow/definition.js.map +1 -1
  97. package/dist/src/workflow/determinism.d.ts +64 -0
  98. package/dist/src/workflow/determinism.d.ts.map +1 -1
  99. package/dist/src/workflow/determinism.js +120 -3
  100. package/dist/src/workflow/determinism.js.map +1 -1
  101. package/dist/src/workflow/errors.d.ts +6 -0
  102. package/dist/src/workflow/errors.d.ts.map +1 -1
  103. package/dist/src/workflow/errors.js +12 -0
  104. package/dist/src/workflow/errors.js.map +1 -1
  105. package/dist/src/workflow/executor.d.ts +56 -0
  106. package/dist/src/workflow/executor.d.ts.map +1 -1
  107. package/dist/src/workflow/executor.js +300 -9
  108. package/dist/src/workflow/executor.js.map +1 -1
  109. package/dist/src/workflow/inbound.d.ts +84 -0
  110. package/dist/src/workflow/inbound.d.ts.map +1 -0
  111. package/dist/src/workflow/inbound.js +65 -0
  112. package/dist/src/workflow/inbound.js.map +1 -0
  113. package/dist/src/workflow/index.d.ts +1 -0
  114. package/dist/src/workflow/index.d.ts.map +1 -1
  115. package/dist/src/workflow/index.js +1 -0
  116. package/dist/src/workflow/index.js.map +1 -1
  117. package/dist/src/workflow/replay.d.ts +24 -2
  118. package/dist/src/workflow/replay.d.ts.map +1 -1
  119. package/dist/src/workflow/replay.js +679 -15
  120. package/dist/src/workflow/replay.js.map +1 -1
  121. package/dist/src/workflows/index.d.ts +1 -1
  122. package/dist/src/workflows/index.d.ts.map +1 -1
  123. package/package.json +1 -1
@@ -1,14 +1,22 @@
1
1
  import { Effect } from 'effect';
2
- import { ContinueAsNewWorkflowError, WorkflowBlockedError } from './errors';
2
+ import * as Schema from 'effect/Schema';
3
+ import { ContinueAsNewWorkflowError, WorkflowBlockedError, WorkflowQueryHandlerMissingError } from './errors';
4
+ import { normalizeInboundArguments, } from './inbound';
3
5
  const DEFAULT_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS = 10_000;
6
+ const MARKER_SIDE_EFFECT = 'temporal-bun-sdk/side-effect';
7
+ const MARKER_VERSION = 'temporal-bun-sdk/get-version';
8
+ const MARKER_PATCH = 'temporal-bun-sdk/patch';
9
+ const MARKER_LOCAL_ACTIVITY = 'temporal-bun-sdk/local-activity';
4
10
  export class WorkflowCommandContext {
5
11
  #info;
6
12
  #guard;
7
13
  #intents = [];
14
+ #activityScheduleEventIds;
8
15
  #sequence = 0;
9
16
  constructor(params) {
10
17
  this.#info = params.info;
11
18
  this.#guard = params.guard;
19
+ this.#activityScheduleEventIds = params.activityScheduleEventIds;
12
20
  }
13
21
  get intents() {
14
22
  return this.#intents;
@@ -25,13 +33,29 @@ export class WorkflowCommandContext {
25
33
  this.#sequence += 1;
26
34
  return seq;
27
35
  }
36
+ resolveScheduledActivityEventId(activityId) {
37
+ return this.#activityScheduleEventIds?.get(activityId);
38
+ }
39
+ previousIntent(sequence) {
40
+ return this.#guard.getPreviousIntent(sequence);
41
+ }
28
42
  get info() {
29
43
  return this.#info;
30
44
  }
31
45
  }
32
46
  export const createWorkflowContext = (params) => {
33
- const commandContext = new WorkflowCommandContext({ info: params.info, guard: params.determinismGuard });
47
+ const commandContext = new WorkflowCommandContext({
48
+ info: params.info,
49
+ guard: params.determinismGuard,
50
+ activityScheduleEventIds: params.activityScheduleEventIds,
51
+ });
52
+ const updateRegistry = new WorkflowUpdateRegistry();
34
53
  const activityResults = params.activityResults ?? new Map();
54
+ const inboundSignals = new WorkflowInboundSignals({
55
+ guard: params.determinismGuard,
56
+ deliveries: params.signalDeliveries,
57
+ });
58
+ const queryRegistry = new WorkflowQueryRegistry({ guard: params.determinismGuard });
35
59
  const activities = {
36
60
  schedule(activityType, args = [], options = {}) {
37
61
  return Effect.sync(() => {
@@ -39,12 +63,27 @@ export const createWorkflowContext = (params) => {
39
63
  commandContext.addIntent(intent);
40
64
  const resolution = activityResults.get(intent.activityId);
41
65
  if (!resolution) {
66
+ // In query mode we must not block; return a deterministic command ref so query
67
+ // handlers can surface the latest known state without introducing new commands.
68
+ if (params.determinismGuard.isQueryMode()) {
69
+ return createCommandRef(intent, { activityId: intent.activityId });
70
+ }
42
71
  throw new WorkflowBlockedError(`Activity ${intent.activityId} pending`);
43
72
  }
44
73
  if (resolution.status === 'failed') {
45
74
  throw resolution.error;
46
75
  }
47
- return createCommandRef(intent, { activityId: intent.activityId, result: resolution.value });
76
+ // When the activity result is already available (e.g., during replay/query),
77
+ // return the resolved value instead of a command reference so workflow code
78
+ // sees the decoded activity output.
79
+ return resolution.value;
80
+ });
81
+ },
82
+ cancel(activityId, options = {}) {
83
+ return Effect.sync(() => {
84
+ const intent = buildRequestCancelActivityIntent(commandContext, activityId, options);
85
+ commandContext.addIntent(intent);
86
+ return createCommandRef(intent, { activityId });
48
87
  });
49
88
  },
50
89
  };
@@ -56,7 +95,22 @@ export const createWorkflowContext = (params) => {
56
95
  }
57
96
  const intent = buildStartTimerIntent(commandContext, options);
58
97
  commandContext.addIntent(intent);
59
- return createCommandRef(intent, { timerId: intent.timerId });
98
+ // If the timer hasn't fired yet, block the workflow so it will resume
99
+ // when the corresponding TimerFired event is observed on replay.
100
+ if (!params.timerResults?.has(intent.timerId)) {
101
+ if (params.determinismGuard.isQueryMode()) {
102
+ return { timerId: intent.timerId };
103
+ }
104
+ throw new WorkflowBlockedError(`Timer ${intent.timerId} pending`);
105
+ }
106
+ return { timerId: intent.timerId };
107
+ });
108
+ },
109
+ cancel(timerId, options = {}) {
110
+ return Effect.sync(() => {
111
+ const intent = buildCancelTimerIntent(commandContext, timerId, options);
112
+ commandContext.addIntent(intent);
113
+ return createCommandRef(intent, { timerId });
60
114
  });
61
115
  },
62
116
  };
@@ -77,11 +131,89 @@ export const createWorkflowContext = (params) => {
77
131
  return createCommandRef(intent);
78
132
  });
79
133
  },
134
+ on(handle, handler, options) {
135
+ return inboundSignals.on(handle, handler, options);
136
+ },
137
+ waitFor(handle, options) {
138
+ return inboundSignals.waitFor(handle, options);
139
+ },
140
+ drain(handle, options) {
141
+ return inboundSignals.drain(handle, options);
142
+ },
143
+ requestCancel(workflowId, options = {}) {
144
+ return Effect.sync(() => {
145
+ const intent = buildRequestCancelExternalWorkflowIntent(commandContext, workflowId, options);
146
+ commandContext.addIntent(intent);
147
+ return createCommandRef(intent);
148
+ });
149
+ },
150
+ };
151
+ const queries = {
152
+ register(handle, resolver, options) {
153
+ const effect = queryRegistry.register(handle, resolver, options);
154
+ // Ensure registration occurs immediately even if caller forgets to yield/await.
155
+ Effect.runSync(effect);
156
+ return effect;
157
+ },
158
+ resolve(handle, input, metadata) {
159
+ return queryRegistry.resolve(handle, input, metadata);
160
+ },
80
161
  };
81
162
  const determinism = {
82
163
  now: () => params.determinismGuard.nextTime(() => Date.now()),
83
164
  random: () => params.determinismGuard.nextRandom(() => Math.random()),
165
+ sideEffect: ({ compute, identifier }) => runSideEffect({
166
+ commandContext,
167
+ markerName: MARKER_SIDE_EFFECT,
168
+ identifier,
169
+ compute,
170
+ }),
171
+ getVersion: ({ changeId, minSupported, maxSupported }) => runGetVersion({ commandContext, changeId, minSupported, maxSupported }),
172
+ patched: (patchId) => runPatchMarker({ commandContext, patchId, deprecated: false }),
173
+ deprecatePatch: (patchId) => {
174
+ runPatchMarker({ commandContext, patchId, deprecated: true });
175
+ },
176
+ recordMarker: (options) => {
177
+ const intent = buildRecordMarkerIntent(commandContext, options.markerName, options.details);
178
+ commandContext.addIntent(intent);
179
+ },
180
+ localActivity: (activityType, args = [], options) => runLocalActivity({
181
+ commandContext,
182
+ activityType,
183
+ args,
184
+ handler: options?.handler,
185
+ activityId: options?.activityId,
186
+ }),
187
+ };
188
+ const upsertSearchAttributes = (attributes) => {
189
+ const intent = buildUpsertSearchAttributesIntent(commandContext, attributes);
190
+ commandContext.addIntent(intent);
84
191
  };
192
+ const upsertMemo = (memo) => {
193
+ const intent = buildModifyWorkflowPropertiesIntent(commandContext, memo);
194
+ commandContext.addIntent(intent);
195
+ };
196
+ const cancelWorkflow = (details) => {
197
+ const intent = buildCancelWorkflowIntent(commandContext, details);
198
+ commandContext.addIntent(intent);
199
+ };
200
+ const updates = {
201
+ register(definition, handler, options) {
202
+ updateRegistry.register(definition, handler, options?.validator);
203
+ },
204
+ registerDefault(handler) {
205
+ updateRegistry.registerDefault(handler);
206
+ },
207
+ };
208
+ if (params.updates) {
209
+ for (const definition of params.updates) {
210
+ // Some callers supply update definitions as metadata and register handlers at runtime.
211
+ // Only auto-register when a handler is present to avoid throwing during workflow replay.
212
+ if ('handler' in definition && typeof definition.handler === 'function') {
213
+ updateRegistry.register(definition, definition.handler, definition.validator);
214
+ }
215
+ }
216
+ }
85
217
  const context = {
86
218
  input: params.input,
87
219
  info: params.info,
@@ -89,7 +221,12 @@ export const createWorkflowContext = (params) => {
89
221
  timers,
90
222
  childWorkflows,
91
223
  signals,
224
+ queries,
92
225
  determinism,
226
+ updates,
227
+ upsertSearchAttributes,
228
+ upsertMemo,
229
+ cancelWorkflow,
93
230
  continueAsNew(options) {
94
231
  return Effect.sync(() => {
95
232
  const intent = buildContinueAsNewIntent(commandContext, options);
@@ -98,7 +235,7 @@ export const createWorkflowContext = (params) => {
98
235
  }).pipe(Effect.flatMap(() => Effect.fail(new ContinueAsNewWorkflowError())));
99
236
  },
100
237
  };
101
- return { context, commandContext };
238
+ return { context, commandContext, queryRegistry, updateRegistry };
102
239
  };
103
240
  const createCommandRef = (intent, extras) => ({
104
241
  id: intent.id,
@@ -132,12 +269,37 @@ const buildScheduleActivityIntent = (ctx, activityType, args, options) => {
132
269
  };
133
270
  const buildStartTimerIntent = (ctx, options) => {
134
271
  const sequence = ctx.nextSequence();
272
+ const previous = ctx.previousIntent(sequence);
273
+ const timeoutMs = previous && previous.kind === 'start-timer' && typeof previous.timeoutMs === 'number'
274
+ ? previous.timeoutMs
275
+ : options.timeoutMs;
135
276
  return {
136
277
  id: `start-timer-${sequence}`,
137
278
  kind: 'start-timer',
138
279
  sequence,
139
280
  timerId: options.timerId ?? `timer-${sequence}`,
140
- timeoutMs: options.timeoutMs,
281
+ timeoutMs,
282
+ };
283
+ };
284
+ const buildCancelTimerIntent = (ctx, timerId, options) => {
285
+ const sequence = ctx.nextSequence();
286
+ return {
287
+ id: `cancel-timer-${sequence}`,
288
+ kind: 'cancel-timer',
289
+ sequence,
290
+ timerId,
291
+ startedEventId: options.startedEventId ? String(options.startedEventId) : undefined,
292
+ };
293
+ };
294
+ const buildRequestCancelActivityIntent = (ctx, activityId, options) => {
295
+ const sequence = ctx.nextSequence();
296
+ const scheduledEventId = options.scheduledEventId ?? ctx.resolveScheduledActivityEventId(activityId);
297
+ return {
298
+ id: `cancel-activity-${sequence}`,
299
+ kind: 'request-cancel-activity',
300
+ sequence,
301
+ activityId,
302
+ scheduledEventId: scheduledEventId !== undefined ? String(scheduledEventId) : undefined,
141
303
  };
142
304
  };
143
305
  const buildStartChildWorkflowIntent = (ctx, workflowType, args, options) => {
@@ -162,6 +324,191 @@ const buildStartChildWorkflowIntent = (ctx, workflowType, args, options) => {
162
324
  cronSchedule: options.cronSchedule,
163
325
  };
164
326
  };
327
+ const buildRequestCancelExternalWorkflowIntent = (ctx, workflowId, options) => {
328
+ const sequence = ctx.nextSequence();
329
+ return {
330
+ id: `cancel-external-${sequence}`,
331
+ kind: 'request-cancel-external-workflow',
332
+ sequence,
333
+ namespace: options.namespace ?? ctx.info.namespace,
334
+ workflowId,
335
+ runId: options.runId,
336
+ childWorkflowOnly: options.childWorkflowOnly ?? false,
337
+ reason: options.reason,
338
+ };
339
+ };
340
+ const buildCancelWorkflowIntent = (ctx, details) => {
341
+ const sequence = ctx.nextSequence();
342
+ return {
343
+ id: `cancel-workflow-${sequence}`,
344
+ kind: 'cancel-workflow',
345
+ sequence,
346
+ details,
347
+ };
348
+ };
349
+ const buildRecordMarkerIntent = (ctx, markerName, details) => {
350
+ const sequence = ctx.nextSequence();
351
+ return {
352
+ id: `record-marker-${sequence}`,
353
+ kind: 'record-marker',
354
+ sequence,
355
+ markerName,
356
+ details,
357
+ };
358
+ };
359
+ const buildUpsertSearchAttributesIntent = (ctx, attributes) => {
360
+ const sequence = ctx.nextSequence();
361
+ return {
362
+ id: `upsert-search-attributes-${sequence}`,
363
+ kind: 'upsert-search-attributes',
364
+ sequence,
365
+ searchAttributes: attributes,
366
+ };
367
+ };
368
+ const buildModifyWorkflowPropertiesIntent = (ctx, memo) => {
369
+ const sequence = ctx.nextSequence();
370
+ return {
371
+ id: `modify-workflow-properties-${sequence}`,
372
+ kind: 'modify-workflow-properties',
373
+ sequence,
374
+ memo,
375
+ };
376
+ };
377
+ const runSideEffect = (params) => {
378
+ const sequence = params.commandContext.nextSequence();
379
+ const previous = params.commandContext.previousIntent(sequence);
380
+ if (previous && previous.kind === 'record-marker' && previous.markerName === params.markerName) {
381
+ const intent = {
382
+ ...previous,
383
+ sequence,
384
+ };
385
+ params.commandContext.addIntent(intent);
386
+ const details = intent.details ?? {};
387
+ if ('result' in details) {
388
+ return details.result;
389
+ }
390
+ return details;
391
+ }
392
+ const value = params.compute();
393
+ const intent = {
394
+ id: `record-marker-${sequence}`,
395
+ kind: 'record-marker',
396
+ sequence,
397
+ markerName: params.markerName,
398
+ details: {
399
+ ...(params.identifier ? { id: params.identifier } : {}),
400
+ result: value,
401
+ },
402
+ };
403
+ params.commandContext.addIntent(intent);
404
+ return value;
405
+ };
406
+ const runGetVersion = (params) => {
407
+ const sequence = params.commandContext.nextSequence();
408
+ const previous = params.commandContext.previousIntent(sequence);
409
+ if (previous && previous.kind === 'record-marker' && previous.markerName === MARKER_VERSION) {
410
+ const intent = { ...previous, sequence };
411
+ params.commandContext.addIntent(intent);
412
+ const details = intent.details ?? {};
413
+ const version = typeof details.version === 'number' ? details.version : undefined;
414
+ if (version === undefined) {
415
+ throw new WorkflowBlockedError('Version marker missing version payload');
416
+ }
417
+ return version;
418
+ }
419
+ if (params.maxSupported < params.minSupported) {
420
+ throw new WorkflowBlockedError('maxSupported version must be >= minSupported version');
421
+ }
422
+ const version = params.maxSupported;
423
+ const intent = {
424
+ id: `version-${sequence}`,
425
+ kind: 'record-marker',
426
+ sequence,
427
+ markerName: MARKER_VERSION,
428
+ details: {
429
+ changeId: params.changeId,
430
+ version,
431
+ },
432
+ };
433
+ params.commandContext.addIntent(intent);
434
+ return version;
435
+ };
436
+ const runPatchMarker = (params) => {
437
+ const sequence = params.commandContext.nextSequence();
438
+ const previous = params.commandContext.previousIntent(sequence);
439
+ if (previous && previous.kind === 'record-marker' && previous.markerName === MARKER_PATCH) {
440
+ const intent = { ...previous, sequence };
441
+ params.commandContext.addIntent(intent);
442
+ return true;
443
+ }
444
+ const intent = {
445
+ id: `patch-${sequence}`,
446
+ kind: 'record-marker',
447
+ sequence,
448
+ markerName: MARKER_PATCH,
449
+ details: {
450
+ patchId: params.patchId,
451
+ deprecated: params.deprecated,
452
+ },
453
+ };
454
+ params.commandContext.addIntent(intent);
455
+ return true;
456
+ };
457
+ const runLocalActivity = (params) => {
458
+ const sequence = params.commandContext.nextSequence();
459
+ const activityId = params.activityId ?? `local-activity-${sequence}`;
460
+ const previous = params.commandContext.previousIntent(sequence);
461
+ const readPreviousResult = (intent) => {
462
+ const details = intent.details ?? {};
463
+ if ('status' in details && details.status === 'failed') {
464
+ const message = typeof details.errorMessage === 'string' ? details.errorMessage : 'Local activity failed';
465
+ throw new Error(message);
466
+ }
467
+ return details.result ?? details.payload;
468
+ };
469
+ if (previous && previous.kind === 'record-marker' && previous.markerName === MARKER_LOCAL_ACTIVITY) {
470
+ const intent = { ...previous, sequence };
471
+ params.commandContext.addIntent(intent);
472
+ return readPreviousResult(intent);
473
+ }
474
+ if (!params.handler) {
475
+ throw new WorkflowBlockedError('Local activity handler is required during initial execution');
476
+ }
477
+ try {
478
+ const value = params.handler(...params.args);
479
+ const intent = {
480
+ id: `local-activity-${sequence}`,
481
+ kind: 'record-marker',
482
+ sequence,
483
+ markerName: MARKER_LOCAL_ACTIVITY,
484
+ details: {
485
+ activityId,
486
+ activityType: params.activityType,
487
+ status: 'completed',
488
+ result: value,
489
+ },
490
+ };
491
+ params.commandContext.addIntent(intent);
492
+ return value;
493
+ }
494
+ catch (error) {
495
+ const message = error instanceof Error ? error.message : String(error);
496
+ const intent = {
497
+ id: `local-activity-${sequence}`,
498
+ kind: 'record-marker',
499
+ sequence,
500
+ markerName: MARKER_LOCAL_ACTIVITY,
501
+ details: {
502
+ activityId,
503
+ activityType: params.activityType,
504
+ status: 'failed',
505
+ errorMessage: message,
506
+ },
507
+ };
508
+ params.commandContext.addIntent(intent);
509
+ throw error;
510
+ }
511
+ };
165
512
  const buildSignalExternalWorkflowIntent = (ctx, signalName, args, options) => {
166
513
  const sequence = ctx.nextSequence();
167
514
  return {
@@ -194,4 +541,177 @@ const buildContinueAsNewIntent = (ctx, options = {}) => {
194
541
  cronSchedule: options.cronSchedule,
195
542
  };
196
543
  };
544
+ class WorkflowInboundSignals {
545
+ #guard;
546
+ #buffers = new Map();
547
+ constructor(params) {
548
+ this.#guard = params.guard;
549
+ for (const delivery of params.deliveries ?? []) {
550
+ const queue = this.#buffers.get(delivery.name) ?? [];
551
+ queue.push({ args: [...delivery.args], metadata: delivery.metadata ?? {} });
552
+ this.#buffers.set(delivery.name, queue);
553
+ }
554
+ }
555
+ on(handle, handler, options) {
556
+ const entry = this.#shift(handle.name);
557
+ if (!entry) {
558
+ return Effect.fail(new WorkflowBlockedError(`Signal "${handle.name}" not yet delivered`));
559
+ }
560
+ const handlerName = resolveHandlerName(options?.name, handler, handle.name);
561
+ return this.#decode(handle, entry)
562
+ .pipe(Effect.tap((payload) => this.#record(handle.name, handlerName, payload, entry.metadata)))
563
+ .pipe(Effect.flatMap((payload) => handler(payload, entry.metadata)));
564
+ }
565
+ waitFor(handle, options) {
566
+ const entry = this.#shift(handle.name);
567
+ if (!entry) {
568
+ return Effect.fail(new WorkflowBlockedError(`Signal "${handle.name}" not yet delivered`));
569
+ }
570
+ const handlerName = options?.name ?? 'waitFor';
571
+ return this.#decode(handle, entry)
572
+ .pipe(Effect.tap((payload) => this.#record(handle.name, handlerName, payload, entry.metadata)))
573
+ .pipe(Effect.map((payload) => ({ payload, metadata: entry.metadata })));
574
+ }
575
+ drain(handle, options) {
576
+ const entries = this.#drain(handle.name);
577
+ if (entries.length === 0) {
578
+ return Effect.fail(new WorkflowBlockedError(`Signal "${handle.name}" not yet delivered`));
579
+ }
580
+ const handlerName = options?.name ?? 'drain';
581
+ return runSequential(entries, (entry) => this.#decode(handle, entry)
582
+ .pipe(Effect.tap((payload) => this.#record(handle.name, handlerName, payload, entry.metadata)))
583
+ .pipe(Effect.map((payload) => ({ payload, metadata: entry.metadata }))));
584
+ }
585
+ #shift(name) {
586
+ const queue = this.#buffers.get(name);
587
+ if (!queue || queue.length === 0) {
588
+ return undefined;
589
+ }
590
+ return queue.shift();
591
+ }
592
+ #drain(name) {
593
+ const queue = this.#buffers.get(name);
594
+ if (!queue || queue.length === 0) {
595
+ return [];
596
+ }
597
+ this.#buffers.set(name, []);
598
+ return queue;
599
+ }
600
+ #decode(handle, entry) {
601
+ const normalized = normalizeInboundArguments(entry.args, handle.decodeArgumentsAsArray);
602
+ return Schema.decodeUnknown(handle.schema)(normalized);
603
+ }
604
+ #record(signalName, handlerName, payload, metadata) {
605
+ return Effect.sync(() => this.#guard.recordSignalDelivery({
606
+ signalName,
607
+ handlerName,
608
+ payload,
609
+ eventId: metadata.eventId ?? null,
610
+ workflowTaskCompletedEventId: metadata.workflowTaskCompletedEventId ?? null,
611
+ identity: metadata.identity ?? null,
612
+ }));
613
+ }
614
+ }
615
+ export class WorkflowUpdateRegistry {
616
+ #handlers = new Map();
617
+ #defaultHandler;
618
+ register(definition, handler, validator) {
619
+ if (typeof handler !== 'function') {
620
+ throw new Error(`Workflow update "${definition.name}" must provide a handler`);
621
+ }
622
+ this.#handlers.set(definition.name, {
623
+ name: definition.name,
624
+ input: definition.input,
625
+ handler: handler,
626
+ validator: validator,
627
+ });
628
+ }
629
+ registerDefault(handler) {
630
+ this.#defaultHandler = {
631
+ name: '__default__',
632
+ input: Schema.Unknown,
633
+ handler,
634
+ };
635
+ }
636
+ get(name) {
637
+ return this.#handlers.get(name);
638
+ }
639
+ getDefault() {
640
+ return this.#defaultHandler;
641
+ }
642
+ list() {
643
+ return Array.from(this.#handlers.values());
644
+ }
645
+ }
646
+ export class WorkflowQueryRegistry {
647
+ #guard;
648
+ #handlers = new Map();
649
+ constructor(params) {
650
+ this.#guard = params.guard;
651
+ }
652
+ register(handle, resolver, options) {
653
+ return Effect.sync(() => {
654
+ this.#handlers.set(handle.name, {
655
+ handle: handle,
656
+ resolver: resolver,
657
+ handlerName: options?.name ?? resolver.name ?? handle.name,
658
+ });
659
+ });
660
+ }
661
+ resolve(handle, input, metadata) {
662
+ const entry = this.#handlers.get(handle.name);
663
+ if (!entry) {
664
+ return Effect.fail(new WorkflowQueryHandlerMissingError(handle.name));
665
+ }
666
+ const invocationMetadata = metadata ?? {};
667
+ const value = (input ?? undefined);
668
+ const resolver = entry.resolver;
669
+ return resolver(value, invocationMetadata)
670
+ .pipe(Effect.tap((result) => this.#recordQueryEvaluation(entry, {
671
+ status: 'success',
672
+ decoded: value,
673
+ result,
674
+ }, invocationMetadata, { name: handle.name, id: invocationMetadata.id ?? `local:${handle.name}` })))
675
+ .pipe(Effect.tapError((error) => this.#recordQueryEvaluation(entry, { status: 'failure', decoded: value, error }, invocationMetadata, {
676
+ name: handle.name,
677
+ id: invocationMetadata.id ?? `local:${handle.name}`,
678
+ })));
679
+ }
680
+ evaluate(request) {
681
+ const entry = this.#handlers.get(request.name);
682
+ if (!entry) {
683
+ return Effect.fail(new WorkflowQueryHandlerMissingError(request.name));
684
+ }
685
+ const metadata = request.metadata ?? {};
686
+ const normalized = normalizeInboundArguments(request.args, entry.handle.decodeInputAsArray) ?? {};
687
+ return Schema.decodeUnknown(entry.handle.inputSchema)(normalized)
688
+ .pipe(Effect.flatMap((decoded) => entry.resolver(decoded, metadata)
689
+ .pipe(Effect.map((result) => ({ status: 'success', decoded, result })))
690
+ .pipe(Effect.catchAll((error) => Effect.succeed({ status: 'failure', decoded, error })))))
691
+ .pipe(Effect.tap((payload) => this.#recordQueryEvaluation(entry, payload, metadata, request)))
692
+ .pipe(Effect.map((payload) => payload.status === 'success'
693
+ ? { request, status: 'success', result: payload.result }
694
+ : { request, status: 'failure', error: payload.error }));
695
+ }
696
+ list() {
697
+ return Array.from(this.#handlers.values());
698
+ }
699
+ #recordQueryEvaluation(entry, payload, metadata, request) {
700
+ return Effect.sync(() => this.#guard.recordQueryEvaluation({
701
+ queryName: request.name,
702
+ handlerName: entry.handlerName,
703
+ request: payload.decoded,
704
+ identity: metadata.identity ?? null,
705
+ queryId: request.id,
706
+ result: payload.status === 'success' ? payload.result : undefined,
707
+ error: payload.status === 'failure' ? payload.error : undefined,
708
+ }));
709
+ }
710
+ }
711
+ const resolveHandlerName = (configured, handler, fallback) => configured ?? handler?.name ?? fallback;
712
+ const runSequential = (entries, factory) => entries.reduce((effect, entry) => effect.pipe(Effect.flatMap((results) => factory(entry).pipe(Effect.map((result) => {
713
+ const next = results.slice();
714
+ next.push(result);
715
+ return next;
716
+ })))), Effect.succeed([]));
197
717
  //# sourceMappingURL=context.js.map