@temporalio/workflow 1.10.1 → 1.10.2

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.
@@ -1,6 +1,9 @@
1
1
  import type { AsyncLocalStorage as ALS } from 'node:async_hooks';
2
2
  import { CancelledFailure, Duration, IllegalStateError } from '@temporalio/common';
3
+ import { msOptionalToNumber } from '@temporalio/common/lib/time';
3
4
  import { untrackPromise } from './stack-helpers';
5
+ import { getActivator } from './global-attributes';
6
+ import { SdkFlags } from './flags';
4
7
 
5
8
  // AsyncLocalStorage is injected via vm module into global scope.
6
9
  // In case Workflow code is imported in Node.js context, replace with an empty class.
@@ -16,7 +19,7 @@ export interface CancellationScopeOptions {
16
19
  /**
17
20
  * Time in milliseconds before the scope cancellation is automatically requested
18
21
  */
19
- timeout?: number;
22
+ timeout?: Duration;
20
23
 
21
24
  /**
22
25
  * If false, prevent outer cancellation from propagating to inner scopes, Activities, timers, and Triggers, defaults to true.
@@ -31,38 +34,56 @@ export interface CancellationScopeOptions {
31
34
  }
32
35
 
33
36
  /**
34
- * In the SDK, Workflows are represented internally by a tree of scopes where the `execute` function runs in the root scope.
35
- * Cancellation propagates from outer scopes to inner ones and is handled by catching {@link CancelledFailure}s
36
- * thrown by cancellable operations (see below).
37
+ * Cancellation Scopes provide the mechanic by which a Workflow may gracefully handle incoming requests for cancellation
38
+ * (e.g. in response to {@link WorkflowHandle.cancel} or through the UI or CLI), as well as request cancelation of
39
+ * cancellable operations it owns (e.g. Activities, Timers, Child Workflows, etc).
37
40
  *
38
- * Scopes are created using the `CancellationScope` constructor or the static helper methods
39
- * {@link cancellable}, {@link nonCancellable} and {@link withTimeout}.
41
+ * Cancellation Scopes form a tree, with the Workflow's main function running in the root scope of that tree.
42
+ * By default, cancellation propagates down from a parent scope to its children and its cancellable operations.
43
+ * A non-cancellable scope can receive cancellation requests, but is never effectively considered as cancelled,
44
+ * thus shieldding its children and cancellable operations from propagation of cancellation requests it receives.
40
45
  *
41
- * When a `CancellationScope` is cancelled, it will propagate cancellation any child scopes and any cancellable
42
- * operations created within it, such as:
46
+ * Scopes are created using the `CancellationScope` constructor or the static helper methods {@link cancellable},
47
+ * {@link nonCancellable} and {@link withTimeout}. `withTimeout` creates a scope that automatically cancels itself after
48
+ * some duration.
43
49
  *
44
- * - Activities
45
- * - Child Workflows
46
- * - Timers (created with the {@link sleep} function)
47
- * - {@link Trigger}s
48
- *
49
- * @example
50
+ * Cancellation of a cancellable scope results in all operations created directly in that scope to throw a
51
+ * {@link CancelledFailure} (either directly, or as the `cause` of an {@link ActivityFailure} or a
52
+ * {@link ChildWorkflowFailure}). Further attempt to create new cancellable scopes or cancellable operations within a
53
+ * scope that has already been cancelled will also immediately throw a {@link CancelledFailure} exception. It is however
54
+ * possible to create a non-cancellable scope at that point; this is often used to execute rollback or cleanup
55
+ * operations. For example:
50
56
  *
51
57
  * ```ts
52
- * await CancellationScope.cancellable(async () => {
53
- * const promise = someActivity();
54
- * CancellationScope.current().cancel(); // Cancels the activity
55
- * await promise; // Throws `ActivityFailure` with `cause` set to `CancelledFailure`
56
- * });
58
+ * async function myWorkflow(...): Promise<void> {
59
+ * try {
60
+ * // This activity runs in the root cancellation scope. Therefore, a cancelation request on
61
+ * // the Workflow execution (e.g. through the UI or CLI) automatically propagates to this
62
+ * // activity. Assuming that the activity properly handle the cancellation request, then the
63
+ * // call below will throw an `ActivityFailure` exception, with `cause` sets to an
64
+ * // instance of `CancelledFailure`.
65
+ * await someActivity();
66
+ * } catch (e) {
67
+ * if (isCancellation(e)) {
68
+ * // Run cleanup activity in a non-cancellable scope
69
+ * await CancellationScope.nonCancellable(async () => {
70
+ * await cleanupActivity();
71
+ * }
72
+ * } else {
73
+ * throw e;
74
+ * }
75
+ * }
76
+ * }
57
77
  * ```
58
78
  *
59
- * @example
79
+ * A cancellable scope may be programatically cancelled by calling {@link cancel|`scope.cancel()`}`. This may be used,
80
+ * for example, to explicitly request cancellation of an Activity or Child Workflow:
60
81
  *
61
82
  * ```ts
62
- * const scope = new CancellationScope();
63
- * const promise = scope.run(someActivity);
64
- * scope.cancel(); // Cancels the activity
65
- * await promise; // Throws `ActivityFailure` with `cause` set to `CancelledFailure`
83
+ * const cancellableActivityScope = new CancellationScope();
84
+ * const activityPromise = cancellableActivityScope.run(() => someActivity());
85
+ * cancellableActivityScope.cancel(); // Cancels the activity
86
+ * await activityPromise; // Throws `ActivityFailure` with `cause` set to `CancelledFailure`
66
87
  * ```
67
88
  */
68
89
  export class CancellationScope {
@@ -72,17 +93,28 @@ export class CancellationScope {
72
93
  protected readonly timeout?: number;
73
94
 
74
95
  /**
75
- * If false, prevent outer cancellation from propagating to inner scopes, Activities, timers, and Triggers, defaults to true.
76
- * (Scope still propagates CancelledFailure thrown from within)
96
+ * If false, then this scope will never be considered cancelled, even if a cancellation request is received (either
97
+ * directly by calling `scope.cancel()` or indirectly by cancelling a cancellable parent scope). This effectively
98
+ * shields the scope's children and cancellable operations from propagation of cancellation requests made on the
99
+ * non-cancellable scope.
100
+ *
101
+ * Note that the Promise returned by the `run` function of non-cancellable scope may still throw a `CancelledFailure`
102
+ * if such an exception is thrown from within that scope (e.g. by directly cancelling a cancellable child scope).
77
103
  */
78
104
  public readonly cancellable: boolean;
105
+
79
106
  /**
80
107
  * An optional CancellationScope (useful for running background tasks), defaults to {@link CancellationScope.current}()
81
108
  */
82
109
  public readonly parent?: CancellationScope;
83
110
 
84
111
  /**
85
- * Rejected when scope cancellation is requested
112
+ * A Promise that throws when a cancellable scope receives a cancellation request, either directly
113
+ * (i.e. `scope.cancel()`), or indirectly (by cancelling a cancellable parent scope).
114
+ *
115
+ * Note that a non-cancellable scope may receive cancellation requests, resulting in the `cancelRequested` promise for
116
+ * that scope to throw, though the scope will not effectively get cancelled (i.e. `consideredCancelled` will still
117
+ * return `false`, and cancellation will not be propagated to child scopes and contained operations).
86
118
  */
87
119
  public readonly cancelRequested: Promise<never>;
88
120
 
@@ -94,12 +126,10 @@ export class CancellationScope {
94
126
  protected readonly reject: (reason?: any) => void;
95
127
 
96
128
  constructor(options?: CancellationScopeOptions) {
97
- this.timeout = options?.timeout;
129
+ this.timeout = msOptionalToNumber(options?.timeout);
98
130
  this.cancellable = options?.cancellable ?? true;
99
131
  this.cancelRequested = new Promise((_, reject) => {
100
- // Typescript does not understand that the Promise executor runs synchronously
101
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
102
- // @ts-ignore
132
+ // @ts-expect-error TSC doesn't understand that the Promise executor runs synchronously
103
133
  this.reject = (err) => {
104
134
  this.#cancelRequested = true;
105
135
  reject(err);
@@ -110,16 +140,36 @@ export class CancellationScope {
110
140
  untrackPromise(this.cancelRequested.catch(() => undefined));
111
141
  if (options?.parent !== NO_PARENT) {
112
142
  this.parent = options?.parent || CancellationScope.current();
113
- this.#cancelRequested = this.parent.#cancelRequested;
114
- this.parent.cancelRequested.catch((err) => {
115
- this.reject(err);
116
- });
143
+ if (
144
+ this.parent.cancellable ||
145
+ (this.parent.#cancelRequested &&
146
+ !getActivator().hasFlag(SdkFlags.NonCancellableScopesAreShieldedFromPropagation))
147
+ ) {
148
+ this.#cancelRequested = this.parent.#cancelRequested;
149
+ untrackPromise(
150
+ this.parent.cancelRequested.catch((err) => {
151
+ this.reject(err);
152
+ })
153
+ );
154
+ } else {
155
+ untrackPromise(
156
+ this.parent.cancelRequested.catch((err) => {
157
+ if (!getActivator().hasFlag(SdkFlags.NonCancellableScopesAreShieldedFromPropagation)) {
158
+ this.reject(err);
159
+ }
160
+ })
161
+ );
162
+ }
117
163
  }
118
164
  }
119
165
 
166
+ /**
167
+ * Whether the scope was effectively cancelled. A non-cancellable scope can never be considered cancelled.
168
+ */
120
169
  public get consideredCancelled(): boolean {
121
170
  return this.#cancelRequested && this.cancellable;
122
171
  }
172
+
123
173
  /**
124
174
  * Activate the scope as current and run `fn`
125
175
  *
@@ -138,17 +188,31 @@ export class CancellationScope {
138
188
  * Could have been written as anonymous function, made into a method for improved stack traces.
139
189
  */
140
190
  protected async runInContext<T>(fn: () => Promise<T>): Promise<T> {
191
+ let timerScope: CancellationScope | undefined;
141
192
  if (this.timeout) {
193
+ timerScope = new CancellationScope();
142
194
  untrackPromise(
143
- sleep(this.timeout).then(
144
- () => this.cancel(),
145
- () => {
146
- // scope was already cancelled, ignore
147
- }
148
- )
195
+ timerScope
196
+ .run(() => sleep(this.timeout as number))
197
+ .then(
198
+ () => this.cancel(),
199
+ () => {
200
+ // scope was already cancelled, ignore
201
+ }
202
+ )
149
203
  );
150
204
  }
151
- return await fn();
205
+ try {
206
+ return await fn();
207
+ } finally {
208
+ if (
209
+ timerScope &&
210
+ !timerScope.consideredCancelled &&
211
+ getActivator().hasFlag(SdkFlags.NonCancellableScopesAreShieldedFromPropagation)
212
+ ) {
213
+ timerScope.cancel();
214
+ }
215
+ }
152
216
  }
153
217
 
154
218
  /**
@@ -177,7 +241,7 @@ export class CancellationScope {
177
241
  }
178
242
 
179
243
  /** Alias to `new CancellationScope({ cancellable: true, timeout }).run(fn)` */
180
- static withTimeout<T>(timeout: number, fn: () => Promise<T>): Promise<T> {
244
+ static withTimeout<T>(timeout: Duration, fn: () => Promise<T>): Promise<T> {
181
245
  return new this({ cancellable: true, timeout }).run(fn);
182
246
  }
183
247
  }
package/src/flags.ts ADDED
@@ -0,0 +1,30 @@
1
+ export type SdkFlag = {
2
+ get id(): number;
3
+ get default(): boolean;
4
+ };
5
+
6
+ const flagsRegistry: Map<number, SdkFlag> = new Map();
7
+
8
+ export const SdkFlags = {
9
+ /**
10
+ * This flag gates multiple fixes related to cancellation scopes and timers introduced in 1.10.2/1.11.0:
11
+ * - Cancellation of a non-cancellable scope no longer propagates to children scopes
12
+ * (see https://github.com/temporalio/sdk-typescript/issues/1423).
13
+ * - CancellationScope.withTimeout(fn) now cancel the timer if `fn` completes before expiration
14
+ * of the timeout, similar to how `condition(fn, timeout)` works.
15
+ * - Timers created using setTimeout can now be intercepted.
16
+ *
17
+ * @since Introduced in 1.10.2/1.11.0.
18
+ */
19
+ NonCancellableScopesAreShieldedFromPropagation: defineFlag(1, false),
20
+ } as const;
21
+
22
+ function defineFlag(id: number, def: boolean): SdkFlag {
23
+ const flag = { id, default: def };
24
+ flagsRegistry.set(id, flag);
25
+ return flag;
26
+ }
27
+
28
+ export function assertValidFlag(id: number): void {
29
+ if (!flagsRegistry.has(id)) throw new TypeError(`Unknown SDK flag: ${id}`);
30
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Overrides some global objects to make them deterministic.
3
+ *
4
+ * @module
5
+ */
6
+ import { msToTs } from '@temporalio/common/lib/time';
7
+ import { CancellationScope } from './cancellation-scope';
8
+ import { DeterminismViolationError } from './errors';
9
+ import { getActivator } from './global-attributes';
10
+ import { SdkFlags } from './flags';
11
+ import { sleep } from './workflow';
12
+ import { untrackPromise } from './stack-helpers';
13
+
14
+ const global = globalThis as any;
15
+ const OriginalDate = globalThis.Date;
16
+
17
+ export function overrideGlobals(): void {
18
+ // Mock any weak reference because GC is non-deterministic and the effect is observable from the Workflow.
19
+ // Workflow developer will get a meaningful exception if they try to use these.
20
+ global.WeakRef = function () {
21
+ throw new DeterminismViolationError('WeakRef cannot be used in Workflows because v8 GC is non-deterministic');
22
+ };
23
+ global.FinalizationRegistry = function () {
24
+ throw new DeterminismViolationError(
25
+ 'FinalizationRegistry cannot be used in Workflows because v8 GC is non-deterministic'
26
+ );
27
+ };
28
+
29
+ global.Date = function (...args: unknown[]) {
30
+ if (args.length > 0) {
31
+ return new (OriginalDate as any)(...args);
32
+ }
33
+ return new OriginalDate(getActivator().now);
34
+ };
35
+
36
+ global.Date.now = function () {
37
+ return getActivator().now;
38
+ };
39
+
40
+ global.Date.parse = OriginalDate.parse.bind(OriginalDate);
41
+ global.Date.UTC = OriginalDate.UTC.bind(OriginalDate);
42
+
43
+ global.Date.prototype = OriginalDate.prototype;
44
+
45
+ const timeoutCancelationScopes = new Map<number, CancellationScope>();
46
+
47
+ /**
48
+ * @param ms sleep duration - number of milliseconds. If given a negative number, value will be set to 1.
49
+ */
50
+ global.setTimeout = function (cb: (...args: any[]) => any, ms: number, ...args: any[]): number {
51
+ ms = Math.max(1, ms);
52
+ const activator = getActivator();
53
+ if (activator.hasFlag(SdkFlags.NonCancellableScopesAreShieldedFromPropagation)) {
54
+ // Capture the sequence number that sleep will allocate
55
+ const seq = activator.nextSeqs.timer;
56
+ const timerScope = new CancellationScope({ cancellable: true });
57
+ const sleepPromise = timerScope.run(() => sleep(ms));
58
+ sleepPromise.then(
59
+ () => {
60
+ timeoutCancelationScopes.delete(seq);
61
+ cb(...args);
62
+ },
63
+ () => {
64
+ timeoutCancelationScopes.delete(seq);
65
+ }
66
+ );
67
+ untrackPromise(sleepPromise);
68
+ timeoutCancelationScopes.set(seq, timerScope);
69
+ return seq;
70
+ } else {
71
+ const seq = activator.nextSeqs.timer++;
72
+ // Create a Promise for AsyncLocalStorage to be able to track this completion using promise hooks.
73
+ new Promise((resolve, reject) => {
74
+ activator.completions.timer.set(seq, { resolve, reject });
75
+ activator.pushCommand({
76
+ startTimer: {
77
+ seq,
78
+ startToFireTimeout: msToTs(ms),
79
+ },
80
+ });
81
+ }).then(
82
+ () => cb(...args),
83
+ () => undefined /* ignore cancellation */
84
+ );
85
+ return seq;
86
+ }
87
+ };
88
+
89
+ global.clearTimeout = function (handle: number): void {
90
+ const activator = getActivator();
91
+ const timerScope = timeoutCancelationScopes.get(handle);
92
+ if (timerScope) {
93
+ timeoutCancelationScopes.delete(handle);
94
+ timerScope.cancel();
95
+ } else {
96
+ activator.nextSeqs.timer++; // Shouldn't increase seq number, but that's the legacy behavior
97
+ activator.completions.timer.delete(handle);
98
+ activator.pushCommand({
99
+ cancelTimer: {
100
+ seq: handle,
101
+ },
102
+ });
103
+ }
104
+ };
105
+
106
+ // activator.random is mutable, don't hardcode its reference
107
+ Math.random = () => getActivator().random();
108
+ }
package/src/interfaces.ts CHANGED
@@ -479,3 +479,8 @@ export type SignalHandlerOptions = { description?: string };
479
479
  * A validator and description of an update handler.
480
480
  */
481
481
  export type UpdateHandlerOptions<Args extends any[]> = { validator?: UpdateValidator<Args>; description?: string };
482
+
483
+ export interface ActivationCompletion {
484
+ commands: coresdk.workflow_commands.IWorkflowCommand[];
485
+ usedInternalFlags: number[];
486
+ }
package/src/internals.ts CHANGED
@@ -32,11 +32,13 @@ import {
32
32
  FileLocation,
33
33
  WorkflowInfo,
34
34
  WorkflowCreateOptionsInternal,
35
+ ActivationCompletion,
35
36
  } from './interfaces';
36
37
  import { type SinkCall } from './sinks';
37
38
  import { untrackPromise } from './stack-helpers';
38
39
  import pkg from './pkg';
39
40
  import { executeWithLifecycleLogging } from './logs';
41
+ import { SdkFlag, assertValidFlag } from './flags';
40
42
 
41
43
  enum StartChildWorkflowExecutionFailedCause {
42
44
  START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_UNSPECIFIED = 0,
@@ -304,12 +306,14 @@ export class Activator implements ActivationHandler {
304
306
  /**
305
307
  * Patches we know the status of for this workflow, as in {@link patched}
306
308
  */
307
- public readonly knownPresentPatches = new Set<string>();
309
+ private readonly knownPresentPatches = new Set<string>();
308
310
 
309
311
  /**
310
312
  * Patches we sent to core {@link patched}
311
313
  */
312
- public readonly sentPatches = new Set<string>();
314
+ private readonly sentPatches = new Set<string>();
315
+
316
+ private readonly knownFlags = new Set<number>();
313
317
 
314
318
  /**
315
319
  * Buffered sink calls per activation
@@ -394,10 +398,11 @@ export class Activator implements ActivationHandler {
394
398
  }
395
399
  }
396
400
 
397
- getAndResetCommands(): coresdk.workflow_commands.IWorkflowCommand[] {
398
- const commands = this.commands;
399
- this.commands = [];
400
- return commands;
401
+ concludeActivation(): ActivationCompletion {
402
+ return {
403
+ commands: this.commands.splice(0),
404
+ usedInternalFlags: [...this.knownFlags],
405
+ };
401
406
  }
402
407
 
403
408
  public async startWorkflowNextHandler({ args }: WorkflowExecuteInput): Promise<any> {
@@ -780,6 +785,41 @@ export class Activator implements ActivationHandler {
780
785
  this.knownPresentPatches.add(activation.patchId);
781
786
  }
782
787
 
788
+ public patchInternal(patchId: string, deprecated: boolean): boolean {
789
+ if (this.workflow === undefined) {
790
+ throw new IllegalStateError('Patches cannot be used before Workflow starts');
791
+ }
792
+ const usePatch = !this.info.unsafe.isReplaying || this.knownPresentPatches.has(patchId);
793
+ // Avoid sending commands for patches core already knows about.
794
+ // This optimization enables development of automatic patching tools.
795
+ if (usePatch && !this.sentPatches.has(patchId)) {
796
+ this.pushCommand({
797
+ setPatchMarker: { patchId, deprecated },
798
+ });
799
+ this.sentPatches.add(patchId);
800
+ }
801
+ return usePatch;
802
+ }
803
+
804
+ // Called early while handling an activation to register known flags
805
+ public addKnownFlags(flags: number[]): void {
806
+ for (const flag of flags) {
807
+ assertValidFlag(flag);
808
+ this.knownFlags.add(flag);
809
+ }
810
+ }
811
+
812
+ public hasFlag(flag: SdkFlag): boolean {
813
+ if (this.knownFlags.has(flag.id)) {
814
+ return true;
815
+ }
816
+ if (!this.info.unsafe.isReplaying && flag.default) {
817
+ this.knownFlags.add(flag.id);
818
+ return true;
819
+ }
820
+ return false;
821
+ }
822
+
783
823
  public removeFromCache(): void {
784
824
  throw new IllegalStateError('removeFromCache activation job should not reach workflow');
785
825
  }
package/src/trigger.ts CHANGED
@@ -26,7 +26,7 @@ export class Trigger<T> implements PromiseLike<T> {
26
26
  constructor() {
27
27
  this.promise = new Promise<T>((resolve, reject) => {
28
28
  const scope = CancellationScope.current();
29
- if (scope.consideredCancelled || scope.cancellable) {
29
+ if (scope.cancellable) {
30
30
  untrackPromise(scope.cancelRequested.catch(reject));
31
31
  }
32
32
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -4,11 +4,10 @@
4
4
  * @module
5
5
  */
6
6
  import { IllegalStateError } from '@temporalio/common';
7
- import { msToTs, tsToMs } from '@temporalio/common/lib/time';
7
+ import { tsToMs } from '@temporalio/common/lib/time';
8
8
  import { composeInterceptors } from '@temporalio/common/lib/interceptors';
9
9
  import { coresdk } from '@temporalio/proto';
10
10
  import { disableStorage } from './cancellation-scope';
11
- import { DeterminismViolationError } from './errors';
12
11
  import { WorkflowInterceptorsFactory } from './interceptors';
13
12
  import { WorkflowCreateOptionsInternal } from './interfaces';
14
13
  import { Activator } from './internals';
@@ -21,72 +20,6 @@ export { PromiseStackStore } from './internals';
21
20
  const global = globalThis as any;
22
21
  const OriginalDate = globalThis.Date;
23
22
 
24
- export function overrideGlobals(): void {
25
- // Mock any weak reference because GC is non-deterministic and the effect is observable from the Workflow.
26
- // Workflow developer will get a meaningful exception if they try to use these.
27
- global.WeakRef = function () {
28
- throw new DeterminismViolationError('WeakRef cannot be used in Workflows because v8 GC is non-deterministic');
29
- };
30
- global.FinalizationRegistry = function () {
31
- throw new DeterminismViolationError(
32
- 'FinalizationRegistry cannot be used in Workflows because v8 GC is non-deterministic'
33
- );
34
- };
35
-
36
- global.Date = function (...args: unknown[]) {
37
- if (args.length > 0) {
38
- return new (OriginalDate as any)(...args);
39
- }
40
- return new OriginalDate(getActivator().now);
41
- };
42
-
43
- global.Date.now = function () {
44
- return getActivator().now;
45
- };
46
-
47
- global.Date.parse = OriginalDate.parse.bind(OriginalDate);
48
- global.Date.UTC = OriginalDate.UTC.bind(OriginalDate);
49
-
50
- global.Date.prototype = OriginalDate.prototype;
51
-
52
- /**
53
- * @param ms sleep duration - number of milliseconds. If given a negative number, value will be set to 1.
54
- */
55
- global.setTimeout = function (cb: (...args: any[]) => any, ms: number, ...args: any[]): number {
56
- const activator = getActivator();
57
- ms = Math.max(1, ms);
58
- const seq = activator.nextSeqs.timer++;
59
- // Create a Promise for AsyncLocalStorage to be able to track this completion using promise hooks.
60
- new Promise((resolve, reject) => {
61
- activator.completions.timer.set(seq, { resolve, reject });
62
- activator.pushCommand({
63
- startTimer: {
64
- seq,
65
- startToFireTimeout: msToTs(ms),
66
- },
67
- });
68
- }).then(
69
- () => cb(...args),
70
- () => undefined /* ignore cancellation */
71
- );
72
- return seq;
73
- };
74
-
75
- global.clearTimeout = function (handle: number): void {
76
- const activator = getActivator();
77
- activator.nextSeqs.timer++;
78
- activator.completions.timer.delete(handle);
79
- activator.pushCommand({
80
- cancelTimer: {
81
- seq: handle,
82
- },
83
- });
84
- };
85
-
86
- // activator.random is mutable, don't hardcode its reference
87
- Math.random = () => getActivator().random();
88
- }
89
-
90
23
  /**
91
24
  * Initialize the isolate runtime.
92
25
  *
@@ -190,6 +123,7 @@ export function activate(activation: coresdk.workflow_activation.WorkflowActivat
190
123
  // timestamp will not be updated for activation that contain only queries
191
124
  activator.now = tsToMs(activation.timestamp);
192
125
  }
126
+ activator.addKnownFlags(activation.availableInternalFlags ?? []);
193
127
 
194
128
  // The Rust Core ensures that these activation fields are not null
195
129
  activator.mutateWorkflowInfo((info) => ({
@@ -249,11 +183,12 @@ export function concludeActivation(): coresdk.workflow_completion.IWorkflowActiv
249
183
  activator.rejectBufferedUpdates();
250
184
  const intercept = composeInterceptors(activator.interceptors.internals, 'concludeActivation', (input) => input);
251
185
  const { info } = activator;
252
- const { commands } = intercept({ commands: activator.getAndResetCommands() });
186
+ const activationCompletion = activator.concludeActivation();
187
+ const { commands } = intercept({ commands: activationCompletion.commands });
253
188
 
254
189
  return {
255
190
  runId: info.runId,
256
- successful: { commands },
191
+ successful: { ...activationCompletion, commands },
257
192
  };
258
193
  }
259
194
 
package/src/workflow.ts CHANGED
@@ -3,7 +3,6 @@ import {
3
3
  ActivityOptions,
4
4
  compileRetryPolicy,
5
5
  extractWorkflowType,
6
- IllegalStateError,
7
6
  LocalActivityOptions,
8
7
  mapToPayloads,
9
8
  QueryDefinition,
@@ -62,7 +61,7 @@ export function addDefaultWorkflowOptions<T extends Workflow>(
62
61
  const { args, workflowId, ...rest } = opts;
63
62
  return {
64
63
  workflowId: workflowId ?? uuid4(),
65
- args: args ?? [],
64
+ args: (args ?? []) as unknown[],
66
65
  cancellationType: ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED,
67
66
  ...rest,
68
67
  };
@@ -987,7 +986,10 @@ export function uuid4(): string {
987
986
  * calls with the same ID, which means all such calls will always return the same value.
988
987
  */
989
988
  export function patched(patchId: string): boolean {
990
- return patchInternal(patchId, false);
989
+ const activator = assertInWorkflowContext(
990
+ 'Workflow.patch(...) and Workflow.deprecatePatch may only be used from a Workflow Execution.'
991
+ );
992
+ return activator.patchInternal(patchId, false);
991
993
  }
992
994
 
993
995
  /**
@@ -1008,29 +1010,10 @@ export function patched(patchId: string): boolean {
1008
1010
  * calls with the same ID, which means all such calls will always return the same value.
1009
1011
  */
1010
1012
  export function deprecatePatch(patchId: string): void {
1011
- patchInternal(patchId, true);
1012
- }
1013
-
1014
- function patchInternal(patchId: string, deprecated: boolean): boolean {
1015
1013
  const activator = assertInWorkflowContext(
1016
1014
  'Workflow.patch(...) and Workflow.deprecatePatch may only be used from a Workflow Execution.'
1017
1015
  );
1018
- // Patch operation does not support interception at the moment, if it did,
1019
- // this would be the place to start the interception chain
1020
-
1021
- if (activator.workflow === undefined) {
1022
- throw new IllegalStateError('Patches cannot be used before Workflow starts');
1023
- }
1024
- const usePatch = !activator.info.unsafe.isReplaying || activator.knownPresentPatches.has(patchId);
1025
- // Avoid sending commands for patches core already knows about.
1026
- // This optimization enables development of automatic patching tools.
1027
- if (usePatch && !activator.sentPatches.has(patchId)) {
1028
- activator.pushCommand({
1029
- setPatchMarker: { patchId, deprecated },
1030
- });
1031
- activator.sentPatches.add(patchId);
1032
- }
1033
- return usePatch;
1016
+ activator.patchInternal(patchId, true);
1034
1017
  }
1035
1018
 
1036
1019
  /**