@temporalio/workflow 1.4.3 → 1.5.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.
@@ -4,7 +4,7 @@ import type { PromiseStackStore } from './internals';
4
4
  * Helper function to remove a promise from being tracked for stack trace query purposes
5
5
  */
6
6
  export function untrackPromise(promise: Promise<unknown>): void {
7
- const store = (globalThis as any).__TEMPORAL__?.promiseStackStore as PromiseStackStore | undefined;
7
+ const store = (globalThis as any).__TEMPORAL__?.activator?.promiseStackStore as PromiseStackStore | undefined;
8
8
  if (!store) return;
9
9
  store.childToParent.delete(promise);
10
10
  store.promiseToStack.delete(promise);
@@ -3,43 +3,20 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { IllegalStateError, ProtoFailure } from '@temporalio/common';
6
+ import { IllegalStateError } from '@temporalio/common';
7
7
  import { msToTs, tsToMs } from '@temporalio/common/lib/time';
8
8
  import { composeInterceptors } from '@temporalio/common/lib/interceptors';
9
9
  import type { coresdk } from '@temporalio/proto';
10
- import { alea } from './alea';
11
10
  import { storage } from './cancellation-scope';
12
11
  import { DeterminismViolationError } from './errors';
13
12
  import { WorkflowInterceptorsFactory } from './interceptors';
14
- import { WorkflowInfo } from './interfaces';
15
- import { InterceptorsImportFunc, state, WorkflowsImportFunc } from './internals';
13
+ import { WorkflowCreateOptionsWithSourceMap } from './interfaces';
14
+ import { Activator, getActivator } from './internals';
16
15
  import { SinkCall } from './sinks';
17
- import type { RawSourceMap } from 'source-map';
18
16
 
19
17
  // Export the type for use on the "worker" side
20
18
  export { PromiseStackStore } from './internals';
21
19
 
22
- export interface WorkflowCreateOptions {
23
- info: WorkflowInfo;
24
- randomnessSeed: number[];
25
- now: number;
26
- patches: string[];
27
- showStackTraceSources: boolean;
28
- }
29
-
30
- export interface WorkflowCreateOptionsWithSourceMap extends WorkflowCreateOptions {
31
- sourceMap: RawSourceMap;
32
- }
33
-
34
- export interface ImportFunctions {
35
- importWorkflows: WorkflowsImportFunc;
36
- importInterceptors: InterceptorsImportFunc;
37
- }
38
- export function setImportFuncs({ importWorkflows, importInterceptors }: ImportFunctions): void {
39
- state.importWorkflows = importWorkflows;
40
- state.importInterceptors = importInterceptors;
41
- }
42
-
43
20
  const global = globalThis as any;
44
21
  const OriginalDate = globalThis.Date;
45
22
 
@@ -60,11 +37,11 @@ export function overrideGlobals(): void {
60
37
  if (args.length > 0) {
61
38
  return new (OriginalDate as any)(...args);
62
39
  }
63
- return new OriginalDate(state.now);
40
+ return new OriginalDate(getActivator().now);
64
41
  };
65
42
 
66
43
  global.Date.now = function () {
67
- return state.now;
44
+ return getActivator().now;
68
45
  };
69
46
 
70
47
  global.Date.parse = OriginalDate.parse.bind(OriginalDate);
@@ -76,12 +53,13 @@ export function overrideGlobals(): void {
76
53
  * @param ms sleep duration - number of milliseconds. If given a negative number, value will be set to 1.
77
54
  */
78
55
  global.setTimeout = function (cb: (...args: any[]) => any, ms: number, ...args: any[]): number {
56
+ const activator = getActivator();
79
57
  ms = Math.max(1, ms);
80
- const seq = state.nextSeqs.timer++;
58
+ const seq = activator.nextSeqs.timer++;
81
59
  // Create a Promise for AsyncLocalStorage to be able to track this completion using promise hooks.
82
60
  new Promise((resolve, reject) => {
83
- state.completions.timer.set(seq, { resolve, reject });
84
- state.pushCommand({
61
+ activator.completions.timer.set(seq, { resolve, reject });
62
+ activator.pushCommand({
85
63
  startTimer: {
86
64
  seq,
87
65
  startToFireTimeout: msToTs(ms),
@@ -95,17 +73,18 @@ export function overrideGlobals(): void {
95
73
  };
96
74
 
97
75
  global.clearTimeout = function (handle: number): void {
98
- state.nextSeqs.timer++;
99
- state.completions.timer.delete(handle);
100
- state.pushCommand({
76
+ const activator = getActivator();
77
+ activator.nextSeqs.timer++;
78
+ activator.completions.timer.delete(handle);
79
+ activator.pushCommand({
101
80
  cancelTimer: {
102
81
  seq: handle,
103
82
  },
104
83
  });
105
84
  };
106
85
 
107
- // state.random is mutable, don't hardcode its reference
108
- Math.random = () => state.random();
86
+ // activator.random is mutable, don't hardcode its reference
87
+ Math.random = () => getActivator().random();
109
88
  }
110
89
 
111
90
  /**
@@ -113,55 +92,36 @@ export function overrideGlobals(): void {
113
92
  *
114
93
  * Sets required internal state and instantiates the workflow and interceptors.
115
94
  */
116
- export async function initRuntime({
117
- info,
118
- randomnessSeed,
119
- now,
120
- patches,
121
- sourceMap,
122
- showStackTraceSources,
123
- }: WorkflowCreateOptionsWithSourceMap): Promise<void> {
124
- // Set the runId globally on the context so it can be retrieved in the case
125
- // of an unhandled promise rejection.
126
- global.__TEMPORAL__.runId = info.runId;
127
- // Set the promiseStackStore so promises can be tracked
128
- global.__TEMPORAL__.promiseStackStore = {
129
- promiseToStack: new Map(),
130
- childToParent: new Map(),
131
- };
95
+ export function initRuntime(options: WorkflowCreateOptionsWithSourceMap): void {
96
+ const { info } = options;
97
+ info.unsafe.now = OriginalDate.now;
98
+ const activator = new Activator(options);
99
+ // There's on activator per workflow instance, set it globally on the context.
100
+ // We do this before importing any user code so user code can statically reference @temporalio/workflow functions
101
+ // as well as Date and Math.random.
102
+ global.__TEMPORAL__.activator = activator;
132
103
 
133
- state.info = info;
134
- state.info.unsafe.now = OriginalDate.now;
135
- state.now = now;
136
- state.random = alea(randomnessSeed);
137
- state.showStackTraceSources = showStackTraceSources;
138
- state.sourceMap = sourceMap;
139
-
140
- if (info.unsafe.isReplaying) {
141
- for (const patch of patches) {
142
- state.knownPresentPatches.add(patch);
143
- }
144
- }
145
-
146
- // @ts-expect-error this is a webpack alias to payloadConverterPath
147
- const customPayloadConverter = (await import('__temporal_custom_payload_converter')).payloadConverter;
104
+ // webpack alias to payloadConverterPath
105
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
106
+ const customPayloadConverter = require('__temporal_custom_payload_converter').payloadConverter;
148
107
  // The `payloadConverter` export is validated in the Worker
149
- if (customPayloadConverter !== undefined) {
150
- state.payloadConverter = customPayloadConverter;
108
+ if (customPayloadConverter != null) {
109
+ activator.payloadConverter = customPayloadConverter;
151
110
  }
152
- // @ts-expect-error this is a webpack alias to failureConverterPath
153
- const customFailureConverter = (await import('__temporal_custom_failure_converter')).failureConverter;
111
+ // webpack alias to failureConverterPath
112
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
113
+ const customFailureConverter = require('__temporal_custom_failure_converter').failureConverter;
154
114
  // The `failureConverter` export is validated in the Worker
155
- if (customFailureConverter !== undefined) {
156
- state.failureConverter = customFailureConverter;
115
+ if (customFailureConverter != null) {
116
+ activator.failureConverter = customFailureConverter;
157
117
  }
158
118
 
159
- const { importWorkflows, importInterceptors } = state;
119
+ const { importWorkflows, importInterceptors } = global.__TEMPORAL__;
160
120
  if (importWorkflows === undefined || importInterceptors === undefined) {
161
- throw new IllegalStateError('Workflow has not been initialized');
121
+ throw new IllegalStateError('Workflow bundle did not register import hooks');
162
122
  }
163
123
 
164
- const interceptors = await importInterceptors();
124
+ const interceptors = importInterceptors();
165
125
  for (const mod of interceptors) {
166
126
  const factory: WorkflowInterceptorsFactory = mod.interceptors;
167
127
  if (factory !== undefined) {
@@ -169,18 +129,18 @@ export async function initRuntime({
169
129
  throw new TypeError(`interceptors must be a function, got: ${factory}`);
170
130
  }
171
131
  const interceptors = factory();
172
- state.interceptors.inbound.push(...(interceptors.inbound ?? []));
173
- state.interceptors.outbound.push(...(interceptors.outbound ?? []));
174
- state.interceptors.internals.push(...(interceptors.internals ?? []));
132
+ activator.interceptors.inbound.push(...(interceptors.inbound ?? []));
133
+ activator.interceptors.outbound.push(...(interceptors.outbound ?? []));
134
+ activator.interceptors.internals.push(...(interceptors.internals ?? []));
175
135
  }
176
136
  }
177
137
 
178
- const mod = await importWorkflows();
138
+ const mod = importWorkflows();
179
139
  const workflow = mod[info.workflowType];
180
140
  if (typeof workflow !== 'function') {
181
141
  throw new TypeError(`'${info.workflowType}' is not a function`);
182
142
  }
183
- state.workflow = workflow;
143
+ activator.workflow = workflow;
184
144
  }
185
145
 
186
146
  /**
@@ -188,23 +148,21 @@ export async function initRuntime({
188
148
  * @returns a boolean indicating whether job was processed or ignored
189
149
  */
190
150
  export function activate(activation: coresdk.workflow_activation.WorkflowActivation, batchIndex: number): void {
191
- const intercept = composeInterceptors(state.interceptors.internals, 'activate', ({ activation, batchIndex }) => {
151
+ const activator = getActivator();
152
+ const intercept = composeInterceptors(activator.interceptors.internals, 'activate', ({ activation, batchIndex }) => {
192
153
  if (batchIndex === 0) {
193
- if (state.info === undefined) {
194
- throw new IllegalStateError('Workflow has not been initialized');
195
- }
196
154
  if (!activation.jobs) {
197
155
  throw new TypeError('Got activation with no jobs');
198
156
  }
199
157
  if (activation.timestamp != null) {
200
158
  // timestamp will not be updated for activation that contain only queries
201
- state.now = tsToMs(activation.timestamp);
159
+ activator.now = tsToMs(activation.timestamp);
202
160
  }
203
161
  if (activation.historyLength == null) {
204
162
  throw new TypeError('Got activation with no historyLength');
205
163
  }
206
- state.info.unsafe.isReplaying = activation.isReplaying ?? false;
207
- state.info.historyLength = activation.historyLength;
164
+ activator.info.unsafe.isReplaying = activation.isReplaying ?? false;
165
+ activator.info.historyLength = activation.historyLength;
208
166
  }
209
167
 
210
168
  // Cast from the interface to the class which has the `variant` attribute.
@@ -223,10 +181,10 @@ export function activate(activation: coresdk.workflow_activation.WorkflowActivat
223
181
  // The only job that can be executed on a completed workflow is a query.
224
182
  // We might get other jobs after completion for instance when a single
225
183
  // activation contains multiple jobs and the first one completes the workflow.
226
- if (state.completed && job.variant !== 'queryWorkflow') {
184
+ if (activator.completed && job.variant !== 'queryWorkflow') {
227
185
  return;
228
186
  }
229
- state.activator[job.variant](variant as any /* TS can't infer this type */);
187
+ activator[job.variant](variant as any /* TS can't infer this type */);
230
188
  if (showUnblockConditions(job)) {
231
189
  tryUnblockConditions();
232
190
  }
@@ -245,18 +203,18 @@ export function activate(activation: coresdk.workflow_activation.WorkflowActivat
245
203
  * Activation failures are handled in the main Node.js isolate.
246
204
  */
247
205
  export function concludeActivation(): coresdk.workflow_completion.IWorkflowActivationCompletion {
248
- const intercept = composeInterceptors(state.interceptors.internals, 'concludeActivation', (input) => input);
249
- const { info } = state;
250
- const { commands } = intercept({ commands: state.commands });
251
- state.commands = [];
206
+ const activator = getActivator();
207
+ const intercept = composeInterceptors(activator.interceptors.internals, 'concludeActivation', (input) => input);
208
+ const { info } = activator;
209
+ const { commands } = intercept({ commands: activator.getAndResetCommands() });
252
210
  return {
253
- runId: info?.runId,
211
+ runId: info.runId,
254
212
  successful: { commands },
255
213
  };
256
214
  }
257
215
 
258
216
  export function getAndResetSinkCalls(): SinkCall[] {
259
- return state.getAndResetSinkCalls();
217
+ return getActivator().getAndResetSinkCalls();
260
218
  }
261
219
 
262
220
  /**
@@ -268,12 +226,12 @@ export function tryUnblockConditions(): number {
268
226
  let numUnblocked = 0;
269
227
  for (;;) {
270
228
  const prevUnblocked = numUnblocked;
271
- for (const [seq, cond] of state.blockedConditions.entries()) {
229
+ for (const [seq, cond] of getActivator().blockedConditions.entries()) {
272
230
  if (cond.fn()) {
273
231
  cond.resolve();
274
232
  numUnblocked++;
275
233
  // It is safe to delete elements during map iteration
276
- state.blockedConditions.delete(seq);
234
+ getActivator().blockedConditions.delete(seq);
277
235
  }
278
236
  }
279
237
  if (prevUnblocked === numUnblocked) {
@@ -290,13 +248,9 @@ export function showUnblockConditions(job: coresdk.workflow_activation.IWorkflow
290
248
  return !job.queryWorkflow && !job.notifyHasPatch;
291
249
  }
292
250
 
293
- export async function dispose(): Promise<void> {
294
- const dispose = composeInterceptors(state.interceptors.internals, 'dispose', async () => {
251
+ export function dispose(): void {
252
+ const dispose = composeInterceptors(getActivator().interceptors.internals, 'dispose', async () => {
295
253
  storage.disable();
296
254
  });
297
- await dispose({});
298
- }
299
-
300
- export function errorToFailure(err: unknown): ProtoFailure {
301
- return state.failureConverter.errorToFailure(err);
255
+ dispose({});
302
256
  }