@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.
- package/README.md +141 -10
- package/dist/src/bin/replay-command.d.ts.map +1 -1
- package/dist/src/bin/replay-command.js +6 -2
- package/dist/src/bin/replay-command.js.map +1 -1
- package/dist/src/bin/temporal-bun.d.ts +1 -1
- package/dist/src/bin/temporal-bun.d.ts.map +1 -1
- package/dist/src/bin/temporal-bun.js +74 -0
- package/dist/src/bin/temporal-bun.js.map +1 -1
- package/dist/src/client/layer.d.ts +2 -2
- package/dist/src/client/layer.d.ts.map +1 -1
- package/dist/src/client/retries.d.ts.map +1 -1
- package/dist/src/client/retries.js +27 -3
- package/dist/src/client/retries.js.map +1 -1
- package/dist/src/client/serialization.d.ts +34 -2
- package/dist/src/client/serialization.d.ts.map +1 -1
- package/dist/src/client/serialization.js +78 -5
- package/dist/src/client/serialization.js.map +1 -1
- package/dist/src/client/types.d.ts +26 -0
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/client.d.ts +22 -6
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +488 -39
- package/dist/src/client.js.map +1 -1
- package/dist/src/common/payloads/codecs.d.ts +38 -0
- package/dist/src/common/payloads/codecs.d.ts.map +1 -0
- package/dist/src/common/payloads/codecs.js +174 -0
- package/dist/src/common/payloads/codecs.js.map +1 -0
- package/dist/src/common/payloads/converter.d.ts +52 -3
- package/dist/src/common/payloads/converter.d.ts.map +1 -1
- package/dist/src/common/payloads/converter.js +340 -2
- package/dist/src/common/payloads/converter.js.map +1 -1
- package/dist/src/common/payloads/failure.d.ts +5 -6
- package/dist/src/common/payloads/failure.d.ts.map +1 -1
- package/dist/src/common/payloads/failure.js +3 -52
- package/dist/src/common/payloads/failure.js.map +1 -1
- package/dist/src/common/payloads/index.d.ts +1 -0
- package/dist/src/common/payloads/index.d.ts.map +1 -1
- package/dist/src/common/payloads/index.js +1 -0
- package/dist/src/common/payloads/index.js.map +1 -1
- package/dist/src/config.d.ts +9 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +62 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/interceptors/client.d.ts +28 -0
- package/dist/src/interceptors/client.d.ts.map +1 -0
- package/dist/src/interceptors/client.js +169 -0
- package/dist/src/interceptors/client.js.map +1 -0
- package/dist/src/interceptors/types.d.ts +33 -0
- package/dist/src/interceptors/types.d.ts.map +1 -0
- package/dist/src/interceptors/types.js +44 -0
- package/dist/src/interceptors/types.js.map +1 -0
- package/dist/src/interceptors/worker.d.ts +22 -0
- package/dist/src/interceptors/worker.d.ts.map +1 -0
- package/dist/src/interceptors/worker.js +156 -0
- package/dist/src/interceptors/worker.js.map +1 -0
- package/dist/src/runtime/cli-layer.d.ts +4 -3
- package/dist/src/runtime/cli-layer.d.ts.map +1 -1
- package/dist/src/runtime/cli-layer.js +5 -2
- package/dist/src/runtime/cli-layer.js.map +1 -1
- package/dist/src/runtime/effect-layers.d.ts +9 -0
- package/dist/src/runtime/effect-layers.d.ts.map +1 -1
- package/dist/src/runtime/effect-layers.js +15 -0
- package/dist/src/runtime/effect-layers.js.map +1 -1
- package/dist/src/worker/concurrency.d.ts +8 -0
- package/dist/src/worker/concurrency.d.ts.map +1 -1
- package/dist/src/worker/concurrency.js +5 -9
- package/dist/src/worker/concurrency.js.map +1 -1
- package/dist/src/worker/runtime.d.ts +5 -0
- package/dist/src/worker/runtime.d.ts.map +1 -1
- package/dist/src/worker/runtime.js +509 -40
- package/dist/src/worker/runtime.js.map +1 -1
- package/dist/src/worker/sticky-cache.d.ts +5 -0
- package/dist/src/worker/sticky-cache.d.ts.map +1 -1
- package/dist/src/worker/sticky-cache.js +26 -0
- package/dist/src/worker/sticky-cache.js.map +1 -1
- package/dist/src/worker/update-protocol.d.ts +33 -0
- package/dist/src/worker/update-protocol.d.ts.map +1 -0
- package/dist/src/worker/update-protocol.js +243 -0
- package/dist/src/worker/update-protocol.js.map +1 -0
- package/dist/src/worker.js +1 -0
- package/dist/src/worker.js.map +1 -1
- package/dist/src/workflow/commands.d.ts +38 -2
- package/dist/src/workflow/commands.d.ts.map +1 -1
- package/dist/src/workflow/commands.js +153 -1
- package/dist/src/workflow/commands.js.map +1 -1
- package/dist/src/workflow/context.d.ts +111 -3
- package/dist/src/workflow/context.d.ts.map +1 -1
- package/dist/src/workflow/context.js +526 -6
- package/dist/src/workflow/context.js.map +1 -1
- package/dist/src/workflow/definition.d.ts +29 -3
- package/dist/src/workflow/definition.d.ts.map +1 -1
- package/dist/src/workflow/definition.js +30 -4
- package/dist/src/workflow/definition.js.map +1 -1
- package/dist/src/workflow/determinism.d.ts +64 -0
- package/dist/src/workflow/determinism.d.ts.map +1 -1
- package/dist/src/workflow/determinism.js +120 -3
- package/dist/src/workflow/determinism.js.map +1 -1
- package/dist/src/workflow/errors.d.ts +6 -0
- package/dist/src/workflow/errors.d.ts.map +1 -1
- package/dist/src/workflow/errors.js +12 -0
- package/dist/src/workflow/errors.js.map +1 -1
- package/dist/src/workflow/executor.d.ts +56 -0
- package/dist/src/workflow/executor.d.ts.map +1 -1
- package/dist/src/workflow/executor.js +300 -9
- package/dist/src/workflow/executor.js.map +1 -1
- package/dist/src/workflow/inbound.d.ts +84 -0
- package/dist/src/workflow/inbound.d.ts.map +1 -0
- package/dist/src/workflow/inbound.js +65 -0
- package/dist/src/workflow/inbound.js.map +1 -0
- package/dist/src/workflow/index.d.ts +1 -0
- package/dist/src/workflow/index.d.ts.map +1 -1
- package/dist/src/workflow/index.js +1 -0
- package/dist/src/workflow/index.js.map +1 -1
- package/dist/src/workflow/replay.d.ts +24 -2
- package/dist/src/workflow/replay.d.ts.map +1 -1
- package/dist/src/workflow/replay.js +679 -15
- package/dist/src/workflow/replay.js.map +1 -1
- package/dist/src/workflows/index.d.ts +1 -1
- package/dist/src/workflows/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
|
-
import
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|