@temporalio/workflow 1.11.7 → 1.12.0-rc.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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { IllegalStateError } from '@temporalio/common';
6
+ import { encodeVersioningBehavior, IllegalStateError, WorkflowFunctionWithOptions } from '@temporalio/common';
7
7
  import { composeInterceptors } from '@temporalio/common/lib/interceptors';
8
8
  import { coresdk } from '@temporalio/proto';
9
9
  import { disableStorage } from './cancellation-scope';
@@ -37,54 +37,68 @@ export function initRuntime(options: WorkflowCreateOptionsInternal): void {
37
37
  // as well as Date and Math.random.
38
38
  setActivatorUntyped(activator);
39
39
 
40
- // webpack alias to payloadConverterPath
41
- // eslint-disable-next-line @typescript-eslint/no-require-imports
42
- const customPayloadConverter = require('__temporal_custom_payload_converter').payloadConverter;
43
- // The `payloadConverter` export is validated in the Worker
44
- if (customPayloadConverter != null) {
45
- activator.payloadConverter = customPayloadConverter;
46
- }
47
- // webpack alias to failureConverterPath
48
- // eslint-disable-next-line @typescript-eslint/no-require-imports
49
- const customFailureConverter = require('__temporal_custom_failure_converter').failureConverter;
50
- // The `failureConverter` export is validated in the Worker
51
- if (customFailureConverter != null) {
52
- activator.failureConverter = customFailureConverter;
53
- }
40
+ activator.rethrowSynchronously = true;
41
+ try {
42
+ // webpack alias to payloadConverterPath
43
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
44
+ const customPayloadConverter = require('__temporal_custom_payload_converter').payloadConverter;
45
+ // The `payloadConverter` export is validated in the Worker
46
+ if (customPayloadConverter != null) {
47
+ activator.payloadConverter = customPayloadConverter;
48
+ }
49
+ // webpack alias to failureConverterPath
50
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
51
+ const customFailureConverter = require('__temporal_custom_failure_converter').failureConverter;
52
+ // The `failureConverter` export is validated in the Worker
53
+ if (customFailureConverter != null) {
54
+ activator.failureConverter = customFailureConverter;
55
+ }
54
56
 
55
- const { importWorkflows, importInterceptors } = global.__TEMPORAL__;
56
- if (importWorkflows === undefined || importInterceptors === undefined) {
57
- throw new IllegalStateError('Workflow bundle did not register import hooks');
58
- }
57
+ const { importWorkflows, importInterceptors } = global.__TEMPORAL__;
58
+ if (importWorkflows === undefined || importInterceptors === undefined) {
59
+ throw new IllegalStateError('Workflow bundle did not register import hooks');
60
+ }
59
61
 
60
- const interceptors = importInterceptors();
61
- for (const mod of interceptors) {
62
- const factory: WorkflowInterceptorsFactory = mod.interceptors;
63
- if (factory !== undefined) {
64
- if (typeof factory !== 'function') {
65
- throw new TypeError(`Failed to initialize workflows interceptors: expected a function, but got: '${factory}'`);
62
+ const interceptors = importInterceptors();
63
+ for (const mod of interceptors) {
64
+ const factory: WorkflowInterceptorsFactory = mod.interceptors;
65
+ if (factory !== undefined) {
66
+ if (typeof factory !== 'function') {
67
+ throw new TypeError(
68
+ `Failed to initialize workflows interceptors: expected a function, but got: '${factory}'`
69
+ );
70
+ }
71
+ const interceptors = factory();
72
+ activator.interceptors.inbound.push(...(interceptors.inbound ?? []));
73
+ activator.interceptors.outbound.push(...(interceptors.outbound ?? []));
74
+ activator.interceptors.internals.push(...(interceptors.internals ?? []));
66
75
  }
67
- const interceptors = factory();
68
- activator.interceptors.inbound.push(...(interceptors.inbound ?? []));
69
- activator.interceptors.outbound.push(...(interceptors.outbound ?? []));
70
- activator.interceptors.internals.push(...(interceptors.internals ?? []));
71
76
  }
72
- }
73
77
 
74
- const mod = importWorkflows();
75
- const workflowFn = mod[activator.info.workflowType];
76
- const defaultWorkflowFn = mod['default'];
77
-
78
- if (typeof workflowFn === 'function') {
79
- activator.workflow = workflowFn;
80
- } else if (typeof defaultWorkflowFn === 'function') {
81
- activator.workflow = defaultWorkflowFn;
82
- } else {
83
- const details =
84
- workflowFn === undefined
85
- ? 'no such function is exported by the workflow bundle'
86
- : `expected a function, but got: '${typeof workflowFn}'`;
87
- throw new TypeError(`Failed to initialize workflow of type '${activator.info.workflowType}': ${details}`);
78
+ const mod = importWorkflows();
79
+ const workflowFn = mod[activator.info.workflowType];
80
+ const defaultWorkflowFn = mod['default'];
81
+
82
+ if (typeof workflowFn === 'function') {
83
+ activator.workflow = workflowFn;
84
+ } else if (typeof defaultWorkflowFn === 'function') {
85
+ activator.workflow = defaultWorkflowFn;
86
+ } else {
87
+ const details =
88
+ workflowFn === undefined
89
+ ? 'no such function is exported by the workflow bundle'
90
+ : `expected a function, but got: '${typeof workflowFn}'`;
91
+ throw new TypeError(`Failed to initialize workflow of type '${activator.info.workflowType}': ${details}`);
92
+ }
93
+ if (isWorkflowFunctionWithOptions(activator.workflow)) {
94
+ if (typeof activator.workflow.workflowDefinitionOptions === 'object') {
95
+ activator.versioningBehavior = activator.workflow.workflowDefinitionOptions.versioningBehavior;
96
+ } else {
97
+ activator.workflowDefinitionOptionsGetter = activator.workflow.workflowDefinitionOptions;
98
+ }
99
+ }
100
+ } finally {
101
+ activator.rethrowSynchronously = false;
88
102
  }
89
103
  }
90
104
 
@@ -111,53 +125,70 @@ function fixPrototypes<X>(obj: X): X {
111
125
  * Initialize the workflow. Or to be exact, _complete_ initialization, as most part has been done in constructor).
112
126
  */
113
127
  export function initialize(initializeWorkflowJob: coresdk.workflow_activation.IInitializeWorkflow): void {
114
- getActivator().initializeWorkflow(initializeWorkflowJob);
128
+ const activator = getActivator();
129
+ activator.rethrowSynchronously = true;
130
+ try {
131
+ activator.initializeWorkflow(initializeWorkflowJob);
132
+ } finally {
133
+ activator.rethrowSynchronously = false;
134
+ }
115
135
  }
116
136
 
117
137
  /**
118
- * Run a chunk of activation jobs
138
+ * Run a chunk of activation jobs.
139
+ *
140
+ * Notice that this function is not async and runs _inside_ the VM context. Therefore, no microtask
141
+ * will get executed _while_ this function is active; they will however get executed _after_ this
142
+ * function returns (i.e. all outstanding microtasks in the VM will get executed before execution
143
+ * resumes out of the VM, in `vm-shared.ts:activate()`).
119
144
  */
120
145
  export function activate(activation: coresdk.workflow_activation.IWorkflowActivation, batchIndex = 0): void {
121
146
  const activator = getActivator();
122
- const intercept = composeInterceptors(activator.interceptors.internals, 'activate', ({ activation }) => {
123
- // Cast from the interface to the class which has the `variant` attribute.
124
- // This is safe because we know that activation is a proto class.
125
- const jobs = activation.jobs as coresdk.workflow_activation.WorkflowActivationJob[];
147
+ activator.rethrowSynchronously = true;
148
+ try {
149
+ const intercept = composeInterceptors(activator.interceptors.internals, 'activate', ({ activation }) => {
150
+ // Cast from the interface to the class which has the `variant` attribute.
151
+ // This is safe because we know that activation is a proto class.
152
+ const jobs = activation.jobs as coresdk.workflow_activation.WorkflowActivationJob[];
126
153
 
127
- // Initialization will have been handled already, but we might still need to start the workflow function
128
- const startWorkflowJob = jobs[0].variant === 'initializeWorkflow' ? jobs.shift()?.initializeWorkflow : undefined;
154
+ // Initialization will have been handled already, but we might still need to start the workflow function
155
+ const startWorkflowJob = jobs[0].variant === 'initializeWorkflow' ? jobs.shift()?.initializeWorkflow : undefined;
129
156
 
130
- for (const job of jobs) {
131
- if (job.variant === undefined) throw new TypeError('Expected job.variant to be defined');
157
+ for (const job of jobs) {
158
+ if (job.variant === undefined) throw new TypeError('Expected job.variant to be defined');
132
159
 
133
- const variant = job[job.variant];
134
- if (!variant) throw new TypeError(`Expected job.${job.variant} to be set`);
160
+ const variant = job[job.variant];
161
+ if (!variant) throw new TypeError(`Expected job.${job.variant} to be set`);
135
162
 
136
- activator[job.variant](variant as any /* TS can't infer this type */);
137
-
138
- if (job.variant !== 'queryWorkflow') tryUnblockConditions();
139
- }
163
+ activator[job.variant](variant as any /* TS can't infer this type */);
140
164
 
141
- if (startWorkflowJob) {
142
- const safeJobTypes: coresdk.workflow_activation.WorkflowActivationJob['variant'][] = [
143
- 'initializeWorkflow',
144
- 'signalWorkflow',
145
- 'doUpdate',
146
- 'cancelWorkflow',
147
- 'updateRandomSeed',
148
- ];
149
- if (jobs.some((job) => !safeJobTypes.includes(job.variant))) {
150
- throw new TypeError(
151
- 'Received both initializeWorkflow and non-signal/non-update jobs in the same activation: ' +
152
- JSON.stringify(jobs.map((job) => job.variant))
153
- );
165
+ if (job.variant !== 'queryWorkflow') tryUnblockConditions();
154
166
  }
155
167
 
156
- activator.startWorkflow(startWorkflowJob);
157
- tryUnblockConditions();
158
- }
159
- });
160
- intercept({ activation, batchIndex });
168
+ if (startWorkflowJob) {
169
+ const safeJobTypes: coresdk.workflow_activation.WorkflowActivationJob['variant'][] = [
170
+ 'initializeWorkflow',
171
+ 'signalWorkflow',
172
+ 'doUpdate',
173
+ 'cancelWorkflow',
174
+ 'updateRandomSeed',
175
+ ];
176
+ if (jobs.some((job) => !safeJobTypes.includes(job.variant))) {
177
+ throw new TypeError(
178
+ 'Received both initializeWorkflow and non-signal/non-update jobs in the same activation: ' +
179
+ JSON.stringify(jobs.map((job) => job.variant))
180
+ );
181
+ }
182
+
183
+ activator.startWorkflow(startWorkflowJob);
184
+
185
+ tryUnblockConditions();
186
+ }
187
+ });
188
+ intercept({ activation, batchIndex });
189
+ } finally {
190
+ activator.rethrowSynchronously = false;
191
+ }
161
192
  }
162
193
 
163
194
  /**
@@ -168,18 +199,26 @@ export function activate(activation: coresdk.workflow_activation.IWorkflowActiva
168
199
  */
169
200
  export function concludeActivation(): coresdk.workflow_completion.IWorkflowActivationCompletion {
170
201
  const activator = getActivator();
171
- activator.rejectBufferedUpdates();
172
- const intercept = composeInterceptors(activator.interceptors.internals, 'concludeActivation', (input) => input);
173
- const activationCompletion = activator.concludeActivation();
174
- const { commands } = intercept({ commands: activationCompletion.commands });
175
- if (activator.completed) {
176
- activator.warnIfUnfinishedHandlers();
202
+ activator.rethrowSynchronously = true;
203
+ try {
204
+ activator.rejectBufferedUpdates();
205
+ const intercept = composeInterceptors(activator.interceptors.internals, 'concludeActivation', (input) => input);
206
+ const activationCompletion = activator.concludeActivation();
207
+ const { commands } = intercept({ commands: activationCompletion.commands });
208
+ if (activator.completed) {
209
+ activator.warnIfUnfinishedHandlers();
210
+ }
211
+ return {
212
+ runId: activator.info.runId,
213
+ successful: {
214
+ ...activationCompletion,
215
+ commands,
216
+ versioningBehavior: encodeVersioningBehavior(activationCompletion.versioningBehavior),
217
+ },
218
+ };
219
+ } finally {
220
+ activator.rethrowSynchronously = false;
177
221
  }
178
-
179
- return {
180
- runId: activator.info.runId,
181
- successful: { ...activationCompletion, commands },
182
- };
183
222
  }
184
223
 
185
224
  /**
@@ -188,28 +227,46 @@ export function concludeActivation(): coresdk.workflow_completion.IWorkflowActiv
188
227
  * @returns number of unblocked conditions.
189
228
  */
190
229
  export function tryUnblockConditions(): number {
191
- let numUnblocked = 0;
192
- for (;;) {
193
- const prevUnblocked = numUnblocked;
194
- for (const [seq, cond] of getActivator().blockedConditions.entries()) {
195
- if (cond.fn()) {
196
- cond.resolve();
197
- numUnblocked++;
198
- // It is safe to delete elements during map iteration
199
- getActivator().blockedConditions.delete(seq);
230
+ const activator = getActivator();
231
+ activator.rethrowSynchronously = true;
232
+ try {
233
+ let numUnblocked = 0;
234
+ for (;;) {
235
+ activator.maybeRethrowWorkflowTaskError();
236
+ const prevUnblocked = numUnblocked;
237
+ for (const [seq, cond] of activator.blockedConditions.entries()) {
238
+ if (cond.fn()) {
239
+ cond.resolve();
240
+ numUnblocked++;
241
+ // It is safe to delete elements during map iteration
242
+ activator.blockedConditions.delete(seq);
243
+ }
244
+ }
245
+ if (prevUnblocked === numUnblocked) {
246
+ break;
200
247
  }
201
248
  }
202
- if (prevUnblocked === numUnblocked) {
203
- break;
204
- }
249
+ return numUnblocked;
250
+ } finally {
251
+ activator.rethrowSynchronously = false;
205
252
  }
206
- return numUnblocked;
207
253
  }
208
254
 
209
255
  export function dispose(): void {
210
- const dispose = composeInterceptors(getActivator().interceptors.internals, 'dispose', async () => {
211
- disableStorage();
212
- disableUpdateStorage();
213
- });
214
- dispose({});
256
+ const activator = getActivator();
257
+ activator.rethrowSynchronously = true;
258
+ try {
259
+ const dispose = composeInterceptors(activator.interceptors.internals, 'dispose', async () => {
260
+ disableStorage();
261
+ disableUpdateStorage();
262
+ });
263
+ dispose({});
264
+ } finally {
265
+ activator.rethrowSynchronously = false;
266
+ }
267
+ }
268
+
269
+ function isWorkflowFunctionWithOptions(obj: any): obj is WorkflowFunctionWithOptions<any[], any> {
270
+ if (obj == null) return false;
271
+ return Object.hasOwn(obj, 'workflowDefinitionOptions');
215
272
  }