@temporalio/workflow 1.5.2 → 1.7.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.
@@ -7,12 +7,13 @@ 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 { storage } from './cancellation-scope';
10
+ import { disableStorage } from './cancellation-scope';
11
11
  import { DeterminismViolationError } from './errors';
12
12
  import { WorkflowInterceptorsFactory } from './interceptors';
13
- import { WorkflowCreateOptionsWithSourceMap } from './interfaces';
13
+ import { WorkflowCreateOptionsWithSourceMap, WorkflowInfo } from './interfaces';
14
14
  import { Activator, getActivator } from './internals';
15
15
  import { SinkCall } from './sinks';
16
+ import { setActivatorUntyped } from './global-attributes';
16
17
 
17
18
  // Export the type for use on the "worker" side
18
19
  export { PromiseStackStore } from './internals';
@@ -22,7 +23,6 @@ const OriginalDate = globalThis.Date;
22
23
 
23
24
  export function overrideGlobals(): void {
24
25
  // Mock any weak reference because GC is non-deterministic and the effect is observable from the Workflow.
25
- // WeakRef is implemented in V8 8.4 which is embedded in node >=14.6.0.
26
26
  // Workflow developer will get a meaningful exception if they try to use these.
27
27
  global.WeakRef = function () {
28
28
  throw new DeterminismViolationError('WeakRef cannot be used in Workflows because v8 GC is non-deterministic');
@@ -93,13 +93,13 @@ export function overrideGlobals(): void {
93
93
  * Sets required internal state and instantiates the workflow and interceptors.
94
94
  */
95
95
  export function initRuntime(options: WorkflowCreateOptionsWithSourceMap): void {
96
- const { info } = options;
96
+ const info: WorkflowInfo = fixPrototypes(options.info);
97
97
  info.unsafe.now = OriginalDate.now;
98
- const activator = new Activator(options);
98
+ const activator = new Activator({ ...options, info });
99
99
  // There's on activator per workflow instance, set it globally on the context.
100
100
  // We do this before importing any user code so user code can statically reference @temporalio/workflow functions
101
101
  // as well as Date and Math.random.
102
- global.__TEMPORAL__.activator = activator;
102
+ setActivatorUntyped(activator);
103
103
 
104
104
  // webpack alias to payloadConverterPath
105
105
  // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -126,7 +126,7 @@ export function initRuntime(options: WorkflowCreateOptionsWithSourceMap): void {
126
126
  const factory: WorkflowInterceptorsFactory = mod.interceptors;
127
127
  if (factory !== undefined) {
128
128
  if (typeof factory !== 'function') {
129
- throw new TypeError(`interceptors must be a function, got: ${factory}`);
129
+ throw new TypeError(`Failed to initialize workflows interceptors: expected a function, but got: '${factory}'`);
130
130
  }
131
131
  const interceptors = factory();
132
132
  activator.interceptors.inbound.push(...(interceptors.inbound ?? []));
@@ -136,11 +136,39 @@ export function initRuntime(options: WorkflowCreateOptionsWithSourceMap): void {
136
136
  }
137
137
 
138
138
  const mod = importWorkflows();
139
- const workflow = mod[info.workflowType];
140
- if (typeof workflow !== 'function') {
141
- throw new TypeError(`'${info.workflowType}' is not a function`);
139
+ const workflowFn = mod[info.workflowType];
140
+ const defaultWorfklowFn = mod['default'];
141
+
142
+ if (typeof workflowFn === 'function') {
143
+ activator.workflow = workflowFn;
144
+ } else if (typeof defaultWorfklowFn === 'function') {
145
+ activator.workflow = defaultWorfklowFn;
146
+ } else {
147
+ const details =
148
+ workflowFn === undefined
149
+ ? 'no such function is exported by the workflow bundle'
150
+ : `expected a function, but got: '${typeof workflowFn}'`;
151
+ throw new TypeError(`Failed to initialize workflow of type '${info.workflowType}': ${details}`);
142
152
  }
143
- activator.workflow = workflow;
153
+ }
154
+
155
+ /**
156
+ * Objects transfered to the VM from outside have prototypes belonging to the
157
+ * outer context, which means that instanceof won't work inside the VM. This
158
+ * function recursively walks over the content of an object, and recreate some
159
+ * of these objects (notably Array, Date and Objects).
160
+ */
161
+ function fixPrototypes<X>(obj: X): X {
162
+ if (obj != null && typeof obj === 'object') {
163
+ switch (Object.getPrototypeOf(obj)?.constructor?.name) {
164
+ case 'Array':
165
+ return Array.from((obj as Array<unknown>).map(fixPrototypes)) as X;
166
+ case 'Date':
167
+ return new Date(obj as unknown as Date) as X;
168
+ default:
169
+ return Object.fromEntries(Object.entries(obj).map(([k, v]): [string, any] => [k, fixPrototypes(v)])) as X;
170
+ }
171
+ } else return obj;
144
172
  }
145
173
 
146
174
  /**
@@ -185,7 +213,7 @@ export function activate(activation: coresdk.workflow_activation.WorkflowActivat
185
213
  return;
186
214
  }
187
215
  activator[job.variant](variant as any /* TS can't infer this type */);
188
- if (showUnblockConditions(job)) {
216
+ if (shouldUnblockConditions(job)) {
189
217
  tryUnblockConditions();
190
218
  }
191
219
  }
@@ -244,13 +272,13 @@ export function tryUnblockConditions(): number {
244
272
  /**
245
273
  * Predicate used to prevent triggering conditions for non-query and non-patch jobs.
246
274
  */
247
- export function showUnblockConditions(job: coresdk.workflow_activation.IWorkflowActivationJob): boolean {
275
+ export function shouldUnblockConditions(job: coresdk.workflow_activation.IWorkflowActivationJob): boolean {
248
276
  return !job.queryWorkflow && !job.notifyHasPatch;
249
277
  }
250
278
 
251
279
  export function dispose(): void {
252
280
  const dispose = composeInterceptors(getActivator().interceptors.internals, 'dispose', async () => {
253
- storage.disable();
281
+ disableStorage();
254
282
  });
255
283
  dispose({});
256
284
  }
package/src/workflow.ts CHANGED
@@ -32,10 +32,12 @@ import {
32
32
  ChildWorkflowOptionsWithDefaults,
33
33
  ContinueAsNew,
34
34
  ContinueAsNewOptions,
35
+ DefaultSignalHandler,
35
36
  EnhancedStackTrace,
37
+ Handler,
36
38
  WorkflowInfo,
37
39
  } from './interfaces';
38
- import { LocalActivityDoBackoff, getActivator } from './internals';
40
+ import { LocalActivityDoBackoff, getActivator, maybeGetActivator } from './internals';
39
41
  import { Sinks } from './sinks';
40
42
  import { untrackPromise } from './stack-helpers';
41
43
  import { ChildWorkflowHandle, ExternalWorkflowHandle } from './workflow-handle';
@@ -692,7 +694,7 @@ export async function startChild<T extends Workflow>(
692
694
  options?: WithWorkflowArgs<T, ChildWorkflowOptions>
693
695
  ): Promise<ChildWorkflowHandle<T>> {
694
696
  const activator = getActivator();
695
- const optionsWithDefaults = addDefaultWorkflowOptions(options ?? {});
697
+ const optionsWithDefaults = addDefaultWorkflowOptions(options ?? ({} as any));
696
698
  const workflowType = typeof workflowTypeOrFunc === 'string' ? workflowTypeOrFunc : workflowTypeOrFunc.name;
697
699
  const execute = composeInterceptors(
698
700
  activator.interceptors.outbound,
@@ -791,7 +793,7 @@ export async function executeChild<T extends Workflow>(
791
793
  options?: WithWorkflowArgs<T, ChildWorkflowOptions>
792
794
  ): Promise<WorkflowResultType<T>> {
793
795
  const activator = getActivator();
794
- const optionsWithDefaults = addDefaultWorkflowOptions(options ?? {});
796
+ const optionsWithDefaults = addDefaultWorkflowOptions(options ?? ({} as any));
795
797
  const workflowType = typeof workflowTypeOrFunc === 'string' ? workflowTypeOrFunc : workflowTypeOrFunc.name;
796
798
  const execute = composeInterceptors(
797
799
  activator.interceptors.outbound,
@@ -843,18 +845,7 @@ export function workflowInfo(): WorkflowInfo {
843
845
  * Returns whether or not code is executing in workflow context
844
846
  */
845
847
  export function inWorkflowContext(): boolean {
846
- try {
847
- getActivator();
848
- return true;
849
- } catch (err: any) {
850
- // Use string comparison in case multiple versions of @temporalio/common are
851
- // installed in which case an instanceof check would fail.
852
- if (err.name === 'IllegalStateError') {
853
- return false;
854
- } else {
855
- throw err;
856
- }
857
- }
848
+ return maybeGetActivator() !== undefined;
858
849
  }
859
850
 
860
851
  /**
@@ -1131,11 +1122,13 @@ function conditionInner(fn: () => boolean): Promise<void> {
1131
1122
  * Definitions are used to register handler in the Workflow via {@link setHandler} and to signal Workflows using a {@link WorkflowHandle}, {@link ChildWorkflowHandle} or {@link ExternalWorkflowHandle}.
1132
1123
  * Definitions can be reused in multiple Workflows.
1133
1124
  */
1134
- export function defineSignal<Args extends any[] = []>(name: string): SignalDefinition<Args> {
1125
+ export function defineSignal<Args extends any[] = [], Name extends string = string>(
1126
+ name: Name
1127
+ ): SignalDefinition<Args, Name> {
1135
1128
  return {
1136
1129
  type: 'signal',
1137
1130
  name,
1138
- };
1131
+ } as SignalDefinition<Args, Name>;
1139
1132
  }
1140
1133
 
1141
1134
  /**
@@ -1144,26 +1137,15 @@ export function defineSignal<Args extends any[] = []>(name: string): SignalDefin
1144
1137
  * Definitions are used to register handler in the Workflow via {@link setHandler} and to query Workflows using a {@link WorkflowHandle}.
1145
1138
  * Definitions can be reused in multiple Workflows.
1146
1139
  */
1147
- export function defineQuery<Ret, Args extends any[] = []>(name: string): QueryDefinition<Ret, Args> {
1140
+ export function defineQuery<Ret, Args extends any[] = [], Name extends string = string>(
1141
+ name: Name
1142
+ ): QueryDefinition<Ret, Args, Name> {
1148
1143
  return {
1149
1144
  type: 'query',
1150
1145
  name,
1151
- };
1146
+ } as QueryDefinition<Ret, Args, Name>;
1152
1147
  }
1153
1148
 
1154
- /**
1155
- * A handler function capable of accepting the arguments for a given SignalDefinition or QueryDefinition.
1156
- */
1157
- export type Handler<
1158
- Ret,
1159
- Args extends any[],
1160
- T extends SignalDefinition<Args> | QueryDefinition<Ret, Args>
1161
- > = T extends SignalDefinition<infer A>
1162
- ? (...args: A) => void | Promise<void>
1163
- : T extends QueryDefinition<infer R, infer A>
1164
- ? (...args: A) => R
1165
- : never;
1166
-
1167
1149
  /**
1168
1150
  * Set a handler function for a Workflow query or signal.
1169
1151
  *
@@ -1178,21 +1160,48 @@ export function setHandler<Ret, Args extends any[], T extends SignalDefinition<A
1178
1160
  ): void {
1179
1161
  const activator = getActivator();
1180
1162
  if (def.type === 'signal') {
1181
- activator.signalHandlers.set(def.name, handler as any);
1182
- const bufferedSignals = activator.bufferedSignals.get(def.name);
1183
- if (bufferedSignals !== undefined && handler !== undefined) {
1184
- activator.bufferedSignals.delete(def.name);
1185
- for (const signal of bufferedSignals) {
1186
- activator.signalWorkflow(signal);
1187
- }
1163
+ if (typeof handler === 'function') {
1164
+ activator.signalHandlers.set(def.name, handler as any);
1165
+ activator.dispatchBufferedSignals();
1166
+ } else if (handler == null) {
1167
+ activator.signalHandlers.delete(def.name);
1168
+ } else {
1169
+ throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`);
1188
1170
  }
1189
1171
  } else if (def.type === 'query') {
1190
- activator.queryHandlers.set(def.name, handler as any);
1172
+ if (typeof handler === 'function') {
1173
+ activator.queryHandlers.set(def.name, handler as any);
1174
+ } else if (handler == null) {
1175
+ activator.queryHandlers.delete(def.name);
1176
+ } else {
1177
+ throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`);
1178
+ }
1191
1179
  } else {
1192
1180
  throw new TypeError(`Invalid definition type: ${(def as any).type}`);
1193
1181
  }
1194
1182
  }
1195
1183
 
1184
+ /**
1185
+ * Set a signal handler function that will handle signals calls for non-registered signal names.
1186
+ *
1187
+ * Signals are dispatched to the default signal handler in the order that they were accepted by the server.
1188
+ *
1189
+ * If this function is called multiple times for a given signal or query name the last handler will overwrite any previous calls.
1190
+ *
1191
+ * @param handler a function that will handle signals for non-registered signal names, or `undefined` to unset the handler.
1192
+ */
1193
+ export function setDefaultSignalHandler(handler: DefaultSignalHandler | undefined): void {
1194
+ const activator = getActivator();
1195
+ if (typeof handler === 'function') {
1196
+ activator.defaultSignalHandler = handler;
1197
+ activator.dispatchBufferedSignals();
1198
+ } else if (handler == null) {
1199
+ activator.defaultSignalHandler = undefined;
1200
+ } else {
1201
+ throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`);
1202
+ }
1203
+ }
1204
+
1196
1205
  /**
1197
1206
  * Updates this Workflow's Search Attributes by merging the provided `searchAttributes` with the existing Search
1198
1207
  * Attributes, `workflowInfo().searchAttributes`.