@temporalio/workflow 1.14.2-canary-release-testing.0 → 1.16.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/src/interfaces.ts CHANGED
@@ -14,6 +14,8 @@ import {
14
14
  Priority,
15
15
  WorkerDeploymentVersion,
16
16
  VersioningBehavior,
17
+ InitialVersioningBehavior,
18
+ SuggestContinueAsNewReason,
17
19
  } from '@temporalio/common';
18
20
  import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
19
21
  import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers';
@@ -45,7 +47,7 @@ export interface WorkflowInfo {
45
47
  * This value may change during the lifetime of an Execution.
46
48
  * @deprecated Use {@link typedSearchAttributes} instead.
47
49
  */
48
- readonly searchAttributes: SearchAttributes; // eslint-disable-line deprecation/deprecation
50
+ readonly searchAttributes: SearchAttributes; // eslint-disable-line @typescript-eslint/no-deprecated
49
51
 
50
52
  /**
51
53
  * Indexed information attached to the Workflow Execution, exposed through an interface.
@@ -122,6 +124,24 @@ export interface WorkflowInfo {
122
124
  */
123
125
  readonly continueAsNewSuggested: boolean;
124
126
 
127
+ /**
128
+ * Whether the workflow's Target Worker Deployment Version has changed from its Pinned Version.
129
+ * When true, the workflow should consider continuing-as-new with
130
+ * `initialVersioningBehavior: 'AUTO_UPGRADE'` to move to the new version.
131
+ *
132
+ * This value changes during the lifetime of an Execution.
133
+ *
134
+ * @experimental Upgrade-on-Continue-as-New is experimental and may change.
135
+ */
136
+ readonly targetWorkerDeploymentVersionChanged: boolean;
137
+
138
+ /**
139
+ * Reason(s) why continue as new is suggested. Can potentially be multiple reasons.
140
+ *
141
+ * @experimental May be removed or changed in the future.
142
+ */
143
+ readonly suggestedContinueAsNewReasons?: SuggestContinueAsNewReason[];
144
+
125
145
  /**
126
146
  * Task queue this Workflow is executing on
127
147
  */
@@ -208,8 +228,6 @@ export interface WorkflowInfo {
208
228
  * executing this task for the first time and has a Deployment Version set, then its ID will be
209
229
  * used. This value may change over the lifetime of the workflow run, but is deterministic and
210
230
  * safe to use for branching.
211
- *
212
- * @experimental Deployment based versioning is experimental and may change in the future.
213
231
  */
214
232
  readonly currentDeploymentVersion?: WorkerDeploymentVersion;
215
233
 
@@ -235,7 +253,22 @@ export interface UnsafeWorkflowInfo {
235
253
  */
236
254
  readonly now: () => number;
237
255
 
256
+ /**
257
+ * Whether the workflow is currently replaying.
258
+ */
238
259
  readonly isReplaying: boolean;
260
+
261
+ /**
262
+ * Whether the workflow is currently replaying history events.
263
+ *
264
+ * This is similar to {@link isReplaying}, but returns `false` during query handlers and update
265
+ * validators, which are live read-only operations that should not be considered as replaying
266
+ * history events.
267
+ *
268
+ * When this property is true, workflow log messages are suppressed and sinks defined with
269
+ * callDuringReplay=false won't get processed.
270
+ */
271
+ readonly isReplayingHistoryEvents: boolean;
239
272
  }
240
273
 
241
274
  /**
@@ -304,7 +337,7 @@ export interface ContinueAsNewOptions {
304
337
  * Searchable attributes to attach to next Workflow run
305
338
  * @deprecated Use {@link typedSearchAttributes} instead.
306
339
  */
307
- searchAttributes?: SearchAttributes; // eslint-disable-line deprecation/deprecation
340
+ searchAttributes?: SearchAttributes; // eslint-disable-line @typescript-eslint/no-deprecated
308
341
  /**
309
342
  * Specifies additional indexed information to attach to the Workflow Execution. More info:
310
343
  * https://docs.temporal.io/docs/typescript/search-attributes
@@ -322,48 +355,80 @@ export interface ContinueAsNewOptions {
322
355
  *
323
356
  * @default 'COMPATIBLE'
324
357
  *
325
- * @deprecated In favor of the new Worker Deployment API.
326
- * @experimental The Worker Versioning API is still being designed. Major changes are expected.
358
+ * @deprecated Worker Versioning is now deprecated. Please use the Worker Deployment API instead: https://docs.temporal.io/worker-deployments
327
359
  */
328
- versioningIntent?: VersioningIntent; // eslint-disable-line deprecation/deprecation
360
+ versioningIntent?: VersioningIntent; // eslint-disable-line @typescript-eslint/no-deprecated
361
+ /**
362
+ * Defines the versioning behavior to be used by the first task of a new workflow run in a continue-as-new chain.
363
+ *
364
+ * @experimental Versioning semantics with continue-as-new are experimental and may change in the future.
365
+ */
366
+ initialVersioningBehavior?: InitialVersioningBehavior;
329
367
  }
330
368
 
331
369
  /**
332
- * Specifies:
333
- * - whether cancellation requests are sent to the Child
334
- * - whether and when a {@link CanceledFailure} is thrown from {@link executeChild} or
335
- * {@link ChildWorkflowHandle.result}
370
+ * Determines:
371
+ * - whether cancellation requests should be propagated from the Parent Workflow to the Child, and
372
+ * - whether and when should the Child's cancellation be reported back to the Parent Workflow
373
+ * (i.e. at which moment should the {@link executeChild}'s or {@link ChildWorkflowHandle.result}'s
374
+ * promise fail with a `ChildWorkflowFailure`, with `cause` set to a `CancelledFailure`).
375
+ *
376
+ * Note that this setting only applies to cancellation originating from an external request for the
377
+ * Parent Workflow itself, or from internal cancellation of the `CancellationScope` in which the
378
+ * Child Workflow call was made. Eventual Cancellation of a Child Workflow on completion of the
379
+ * Parent Workflow is controlled by the {@link ParentClosePolicy} setting.
336
380
  *
337
- * @default {@link ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED}
381
+ * @default ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED
338
382
  */
339
- export type ChildWorkflowCancellationType =
340
- (typeof ChildWorkflowCancellationType)[keyof typeof ChildWorkflowCancellationType];
383
+ // MAINTENANCE: Keep this typedoc in sync with the `ChildWorkflowOptions.cancellationType` field
341
384
  export const ChildWorkflowCancellationType = {
342
385
  /**
343
- * Don't send a cancellation request to the Child.
386
+ * Do not propagate cancellation requests to the Child, and immediately report cancellation
387
+ * to the caller.
344
388
  */
345
389
  ABANDON: 'ABANDON',
346
390
 
347
391
  /**
348
- * Send a cancellation request to the Child. Immediately throw the error.
392
+ * Propagate cancellation request from the Parent Workflow to the Child, yet _immediately_ report
393
+ * cancellation to the caller, i.e. without waiting for the server to confirm the cancellation
394
+ * request.
395
+ *
396
+ * Note that this cancellation type provides no guarantee, from the Parent-side, that the
397
+ * cancellation request will actually be atomically added to the Child workflow's history.
398
+ * In particular, the Child may complete (either successfully or uncessfully) before the
399
+ * cancellation is delivered, resulting in a situation where the Parent workflow thinks its child
400
+ * was cancelled, but the child actually completed successfully.
401
+ *
402
+ * To guarantee that the Child will eventually be notified of the cancellation request,
403
+ * use {@link WAIT_CANCELLATION_REQUESTED}.
349
404
  */
350
405
  TRY_CANCEL: 'TRY_CANCEL',
351
406
 
352
407
  /**
353
- * Send a cancellation request to the Child. The Child may respect cancellation, in which case an error will be thrown
354
- * when cancellation has completed, and {@link isCancellation}(error) will be true. On the other hand, the Child may
355
- * ignore the cancellation request, in which case an error might be thrown with a different cause, or the Child may
356
- * complete successfully.
408
+ * Propagate cancellation request from the Parent Workflow to the Child, then wait for the server
409
+ * to confirm that the Child Workflow cancellation request was recorded in its history.
357
410
  *
358
- * @default
411
+ * This cancellation type guarantees that the Child will eventually be notified of the
412
+ * cancellation request (that is, unless the Child terminates inbetween due to unexpected causes).
359
413
  */
360
- WAIT_CANCELLATION_COMPLETED: 'WAIT_CANCELLATION_COMPLETED',
414
+ WAIT_CANCELLATION_REQUESTED: 'WAIT_CANCELLATION_REQUESTED',
361
415
 
362
416
  /**
363
- * Send a cancellation request to the Child. Throw the error once the Server receives the Child cancellation request.
417
+ * Propagate cancellation request from the Parent Workflow to the Child, then wait for completion
418
+ * of the Child Workflow.
419
+ *
420
+ * The Child may respect cancellation, in which case the Parent's `executeChild` or
421
+ * `ChildWorkflowHandle.result` promise will fail with a `ChildWorkflowFailure`, with `cause`
422
+ * set to a `CancelledFailure`. On the other hand, the Child may ignore the cancellation request,
423
+ * in which case the corresponding promise will either resolve with a result (if Child completed
424
+ * successfully) or reject with a different cause (if Child completed uncessfully).
425
+ *
426
+ * @default
364
427
  */
365
- WAIT_CANCELLATION_REQUESTED: 'WAIT_CANCELLATION_REQUESTED',
428
+ WAIT_CANCELLATION_COMPLETED: 'WAIT_CANCELLATION_COMPLETED',
366
429
  } as const;
430
+ export type ChildWorkflowCancellationType =
431
+ (typeof ChildWorkflowCancellationType)[keyof typeof ChildWorkflowCancellationType];
367
432
 
368
433
  // ts-prune-ignore-next
369
434
  export const [encodeChildWorkflowCancellationType, decodeChildWorkflowCancellationType] = makeProtoEnumConverters<
@@ -387,7 +452,6 @@ export const [encodeChildWorkflowCancellationType, decodeChildWorkflowCancellati
387
452
  *
388
453
  * @see {@link https://docs.temporal.io/concepts/what-is-a-parent-close-policy/ | Parent Close Policy}
389
454
  */
390
- export type ParentClosePolicy = (typeof ParentClosePolicy)[keyof typeof ParentClosePolicy];
391
455
  export const ParentClosePolicy = {
392
456
  /**
393
457
  * When the Parent is Closed, the Child is Terminated.
@@ -413,29 +477,30 @@ export const ParentClosePolicy = {
413
477
  *
414
478
  * @deprecated Either leave property `undefined`, or set an explicit policy instead.
415
479
  */
416
- PARENT_CLOSE_POLICY_UNSPECIFIED: undefined, // eslint-disable-line deprecation/deprecation
480
+ PARENT_CLOSE_POLICY_UNSPECIFIED: undefined,
417
481
 
418
482
  /**
419
483
  * When the Parent is Closed, the Child is Terminated.
420
484
  *
421
485
  * @deprecated Use {@link ParentClosePolicy.TERMINATE} instead.
422
486
  */
423
- PARENT_CLOSE_POLICY_TERMINATE: 'TERMINATE', // eslint-disable-line deprecation/deprecation
487
+ PARENT_CLOSE_POLICY_TERMINATE: 'TERMINATE',
424
488
 
425
489
  /**
426
490
  * When the Parent is Closed, nothing is done to the Child.
427
491
  *
428
492
  * @deprecated Use {@link ParentClosePolicy.ABANDON} instead.
429
493
  */
430
- PARENT_CLOSE_POLICY_ABANDON: 'ABANDON', // eslint-disable-line deprecation/deprecation
494
+ PARENT_CLOSE_POLICY_ABANDON: 'ABANDON',
431
495
 
432
496
  /**
433
497
  * When the Parent is Closed, the Child is Cancelled.
434
498
  *
435
499
  * @deprecated Use {@link ParentClosePolicy.REQUEST_CANCEL} instead.
436
500
  */
437
- PARENT_CLOSE_POLICY_REQUEST_CANCEL: 'REQUEST_CANCEL', // eslint-disable-line deprecation/deprecation
501
+ PARENT_CLOSE_POLICY_REQUEST_CANCEL: 'REQUEST_CANCEL',
438
502
  } as const;
503
+ export type ParentClosePolicy = (typeof ParentClosePolicy)[keyof typeof ParentClosePolicy];
439
504
 
440
505
  // ts-prune-ignore-next
441
506
  export const [encodeParentClosePolicy, decodeParentClosePolicy] = makeProtoEnumConverters<
@@ -471,19 +536,26 @@ export interface ChildWorkflowOptions extends Omit<CommonWorkflowOptions, 'workf
471
536
  taskQueue?: string;
472
537
 
473
538
  /**
474
- * Specifies:
475
- * - whether cancellation requests are sent to the Child
476
- * - whether and when an error is thrown from {@link executeChild} or
477
- * {@link ChildWorkflowHandle.result}
539
+ * Determines:
540
+ * - whether cancellation requests should be propagated from the Parent Workflow to the Child, and
541
+ * - whether and when should the Child's cancellation be reported back to the Parent Workflow
542
+ * (i.e. at which moment should the {@link executeChild}'s or {@link ChildWorkflowHandle.result}'s
543
+ * promise fail with a `ChildWorkflowFailure`, with `cause` set to a `CancelledFailure`).
544
+ *
545
+ * Note that this setting only applies to cancellation originating from an external request for the
546
+ * Parent Workflow itself, or from internal cancellation of the `CancellationScope` in which the
547
+ * Child Workflow call was made. Eventual Cancellation of a Child Workflow on completion of the
548
+ * Parent Workflow is controlled by the {@link ParentClosePolicy} setting.
478
549
  *
479
- * @default {@link ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED}
550
+ * @default ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED
480
551
  */
552
+ // MAINTENANCE: Keep this typedoc in sync with the `ChildWorkflowCancellationType` enum
481
553
  cancellationType?: ChildWorkflowCancellationType;
482
554
 
483
555
  /**
484
556
  * Specifies how the Child reacts to the Parent Workflow reaching a Closed state.
485
557
  *
486
- * @default {@link ParentClosePolicy.PARENT_CLOSE_POLICY_TERMINATE}
558
+ * @default ParentClosePolicy.PARENT_CLOSE_POLICY_TERMINATE
487
559
  */
488
560
  parentClosePolicy?: ParentClosePolicy;
489
561
 
@@ -493,10 +565,9 @@ export interface ChildWorkflowOptions extends Omit<CommonWorkflowOptions, 'workf
493
565
  *
494
566
  * @default 'COMPATIBLE'
495
567
  *
496
- * @deprecated In favor of the new Worker Deployment API.
497
- * @experimental The Worker Versioning API is still being designed. Major changes are expected.
568
+ * @deprecated Worker Versioning is now deprecated. Please use the Worker Deployment API instead: https://docs.temporal.io/worker-deployments
498
569
  */
499
- versioningIntent?: VersioningIntent; // eslint-disable-line deprecation/deprecation
570
+ versioningIntent?: VersioningIntent; // eslint-disable-line @typescript-eslint/no-deprecated
500
571
  }
501
572
 
502
573
  export type RequiredChildWorkflowOptions = Required<Pick<ChildWorkflowOptions, 'workflowId' | 'cancellationType'>> & {
@@ -581,6 +652,7 @@ export interface WorkflowCreateOptionsInternal extends WorkflowCreateOptions {
581
652
  sourceMap: RawSourceMap;
582
653
  registeredActivityNames: Set<string>;
583
654
  getTimeOfDay(): bigint;
655
+ stackTracesEnabled: boolean;
584
656
  }
585
657
 
586
658
  /**
package/src/internals.ts CHANGED
@@ -457,6 +457,8 @@ export class Activator implements ActivationHandler {
457
457
 
458
458
  public readonly workflowSandboxDestructors: (() => void)[] = [];
459
459
 
460
+ protected readonly stackTracesEnabled: boolean;
461
+
460
462
  constructor({
461
463
  info,
462
464
  now,
@@ -465,6 +467,7 @@ export class Activator implements ActivationHandler {
465
467
  getTimeOfDay,
466
468
  randomnessSeed,
467
469
  registeredActivityNames,
470
+ stackTracesEnabled,
468
471
  }: WorkflowCreateOptionsInternal) {
469
472
  this.getTimeOfDay = getTimeOfDay;
470
473
  this.info = info;
@@ -473,6 +476,7 @@ export class Activator implements ActivationHandler {
473
476
  this.sourceMap = sourceMap;
474
477
  this.random = alea(randomnessSeed);
475
478
  this.registeredActivityNames = registeredActivityNames;
479
+ this.stackTracesEnabled = stackTracesEnabled;
476
480
  }
477
481
 
478
482
  /**
@@ -483,6 +487,9 @@ export class Activator implements ActivationHandler {
483
487
  }
484
488
 
485
489
  protected getStackTraces(): Stack[] {
490
+ if (!this.stackTracesEnabled) {
491
+ throw new IllegalStateError('Workflow stack traces are not enabled on this worker');
492
+ }
486
493
  const { childToParent, promiseToStack } = this.promiseStackStore;
487
494
  const internalNodes = [...childToParent.values()].reduce((acc, curr) => {
488
495
  for (const p of curr) {
@@ -858,12 +865,26 @@ export class Activator implements ActivationHandler {
858
865
  let input: UpdateInput;
859
866
  try {
860
867
  if (runValidator && entry.validator) {
861
- const validate = composeInterceptors(
862
- interceptors,
863
- 'validateUpdate',
864
- this.validateUpdateNextHandler.bind(this, entry.validator)
865
- );
866
- validate(makeInput());
868
+ // Temporarily mark as not replaying history events during validator execution
869
+ // so that logging is permitted. Validators are live read-only operations.
870
+ const wasReplayingHistoryEvents = this.info.unsafe.isReplayingHistoryEvents;
871
+ this.mutateWorkflowInfo((info) => ({
872
+ ...info,
873
+ unsafe: { ...info.unsafe, isReplayingHistoryEvents: false },
874
+ }));
875
+ try {
876
+ const validate = composeInterceptors(
877
+ interceptors,
878
+ 'validateUpdate',
879
+ this.validateUpdateNextHandler.bind(this, entry.validator)
880
+ );
881
+ validate(makeInput());
882
+ } finally {
883
+ this.mutateWorkflowInfo((info) => ({
884
+ ...info,
885
+ unsafe: { ...info.unsafe, isReplayingHistoryEvents: wasReplayingHistoryEvents },
886
+ }));
887
+ }
867
888
  }
868
889
  input = makeInput();
869
890
  } catch (error) {
@@ -930,7 +951,6 @@ export class Activator implements ActivationHandler {
930
951
  const update = this.bufferedUpdates.shift();
931
952
  if (update) {
932
953
  this.rejectUpdate(
933
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
934
954
  update.protocolInstanceId!,
935
955
  ApplicationFailure.nonRetryable(`No registered handler for update: ${update.name}`)
936
956
  );
@@ -997,7 +1017,7 @@ export class Activator implements ActivationHandler {
997
1017
  while (bufferedSignals.length) {
998
1018
  if (this.defaultSignalHandler) {
999
1019
  // We have a default signal handler, so all signals are dispatchable
1000
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1020
+
1001
1021
  this.signalWorkflow(bufferedSignals.shift()!);
1002
1022
  } else {
1003
1023
  const foundIndex = bufferedSignals.findIndex((signal) => this.signalHandlers.has(signal.signalName as string));
package/src/metrics.ts CHANGED
@@ -48,6 +48,9 @@ class WorkflowMetricMeterImpl implements MetricMeter {
48
48
  }
49
49
 
50
50
  class WorkflowMetricCounter implements MetricCounter {
51
+ public readonly kind = 'counter';
52
+ public readonly valueType = 'int';
53
+
51
54
  constructor(
52
55
  public readonly name: string,
53
56
  public readonly unit: string | undefined,
@@ -70,6 +73,8 @@ class WorkflowMetricCounter implements MetricCounter {
70
73
  }
71
74
 
72
75
  class WorkflowMetricHistogram implements MetricHistogram {
76
+ public readonly kind = 'histogram';
77
+
73
78
  constructor(
74
79
  public readonly name: string,
75
80
  public readonly valueType: NumericMetricValueType,
@@ -93,6 +98,8 @@ class WorkflowMetricHistogram implements MetricHistogram {
93
98
  }
94
99
 
95
100
  class WorkflowMetricGauge implements MetricGauge {
101
+ public readonly kind = 'gauge';
102
+
96
103
  constructor(
97
104
  public readonly name: string,
98
105
  public readonly valueType: NumericMetricValueType,
package/src/nexus.ts CHANGED
@@ -2,6 +2,8 @@ import * as nexus from 'nexus-rpc';
2
2
  import { msOptionalToTs } from '@temporalio/common/lib/time';
3
3
  import { userMetadataToPayload } from '@temporalio/common/lib/user-metadata';
4
4
  import { composeInterceptors } from '@temporalio/common/lib/interceptors';
5
+ import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers';
6
+ import type { coresdk } from '@temporalio/proto';
5
7
  import { CancellationScope } from './cancellation-scope';
6
8
  import { getActivator } from './global-attributes';
7
9
  import { untrackPromise } from './stack-helpers';
@@ -12,7 +14,7 @@ import { StartNexusOperationInput, StartNexusOperationOutput, StartNexusOperatio
12
14
  *
13
15
  * @experimental Nexus support in Temporal SDK is experimental.
14
16
  */
15
- export interface NexusClient<T extends nexus.ServiceDefinition> {
17
+ export interface NexusServiceClient<T extends nexus.ServiceDefinition> {
16
18
  /**
17
19
  * Start a Nexus Operation and wait for its completion taking a {@link nexus.operation}.
18
20
  * Returns the operation's result.
@@ -102,9 +104,9 @@ export interface NexusOperationHandle<T> {
102
104
  }
103
105
 
104
106
  /**
105
- * Options for {@link createNexusClient}.
107
+ * Options for {@link createNexusServiceClient}.
106
108
  */
107
- export interface NexusClientOptions<T> {
109
+ export interface NexusServiceClientOptions<T> {
108
110
  endpoint: string;
109
111
  service: T;
110
112
  }
@@ -114,8 +116,10 @@ export interface NexusClientOptions<T> {
114
116
  *
115
117
  * @experimental Nexus support in Temporal SDK is experimental.
116
118
  */
117
- export function createNexusClient<T extends nexus.ServiceDefinition>(options: NexusClientOptions<T>): NexusClient<T> {
118
- class NexusClientImpl<T extends nexus.ServiceDefinition> implements NexusClient<T> {
119
+ export function createNexusServiceClient<T extends nexus.ServiceDefinition>(
120
+ options: NexusServiceClientOptions<T>
121
+ ): NexusServiceClient<T> {
122
+ class NexusServiceClientImpl<T extends nexus.ServiceDefinition> implements NexusServiceClient<T> {
119
123
  async executeOperation<O extends T['operations'][keyof T['operations']]>(
120
124
  operation: string | T['operations'][nexus.OperationKey<T['operations']>],
121
125
  input: nexus.OperationInput<T['operations'][nexus.OperationKey<T['operations']>]>,
@@ -146,17 +150,18 @@ export function createNexusClient<T extends nexus.ServiceDefinition>(options: Ne
146
150
  startNexusOperationNextHandler
147
151
  );
148
152
 
149
- // TODO: Do we want to make the interceptor async like we do for child workflow? That seems redundant.
150
- // REVIEW: I ended up changing this so that the interceptor returns a Promise<StartNexusOperationOutput>,
151
- // and the result promise is contained in that Output object. As a consequence of this,
152
- // the result promise/completion does not exist until the StartNexusOperation event is received.
153
- // That's totally different from what we did in ChildWorkflow, but I think that's cleaner from
154
- // interceptors point of view, and will make it easier to extend the API in the future.
153
+ // The interceptor returns a Promise<StartNexusOperationOutput>, with the result promise contained
154
+ // in that Output object. As a consequence of this, the result promise/completion does not exist
155
+ // until the StartNexusOperation event is received. This is totally different from what we did in
156
+ // ChildWorkflow, but is much cleaner from the interceptors point of view.
155
157
  const { token, result: resultPromise } = await execute({
156
158
  endpoint: options.endpoint,
157
159
  service: options.service.name,
158
160
  operation: opName,
159
- options: operationOptions ?? {},
161
+ options: {
162
+ ...operationOptions,
163
+ cancellationType: operationOptions?.cancellationType ?? 'WAIT_CANCELLATION_COMPLETED',
164
+ },
160
165
  headers: {},
161
166
  seq,
162
167
  input,
@@ -173,7 +178,7 @@ export function createNexusClient<T extends nexus.ServiceDefinition>(options: Ne
173
178
  }
174
179
  }
175
180
 
176
- return new NexusClientImpl<T>();
181
+ return new NexusServiceClientImpl<T>();
177
182
  }
178
183
 
179
184
  function startNexusOperationNextHandler({
@@ -220,7 +225,9 @@ function startNexusOperationNextHandler({
220
225
  nexusHeader: headers,
221
226
  input: activator.payloadConverter.toPayload(input),
222
227
  scheduleToCloseTimeout: msOptionalToTs(options?.scheduleToCloseTimeout),
223
- // FIXME(nexus-post-initial-release): cancellationType is not supported yet
228
+ scheduleToStartTimeout: msOptionalToTs(options?.scheduleToStartTimeout),
229
+ startToCloseTimeout: msOptionalToTs(options?.startToCloseTimeout),
230
+ cancellationType: encodeNexusOperationCancellationType(options?.cancellationType),
224
231
  },
225
232
  userMetadata: userMetadataToPayload(activator.payloadConverter, options?.summary, undefined),
226
233
  });
@@ -231,3 +238,76 @@ function startNexusOperationNextHandler({
231
238
  });
232
239
  });
233
240
  }
241
+
242
+ /**
243
+ * Determines:
244
+ * - whether cancellation requests should be propagated from the Workflow to the Nexus Operation
245
+ * - whether and when should the Operation's cancellation be reported back to the Workflow
246
+ * (i.e. at which moment should the operation's result promise fail with a `NexusOperationFailure`,
247
+ * with `cause` set to a `CancelledFailure`).
248
+ *
249
+ * Note that this setting only applies to cancellation originating from an external request for the
250
+ * Workflow itself, or from internal cancellation of the `CancellationScope` in which the
251
+ * Operation call was made.
252
+ *
253
+ * @experimental Nexus support in Temporal SDK is experimental.
254
+ */
255
+ // MAINTENANCE: Keep this typedoc in sync with the `StartNexusOperationOptions.cancellationType` field
256
+ export const NexusOperationCancellationType = {
257
+ /**
258
+ * Do not propagate cancellation requests to the Nexus Operation, and immediately report
259
+ * cancellation to the caller.
260
+ */
261
+ ABANDON: 'ABANDON',
262
+
263
+ /**
264
+ * Initiate a cancellation request for the Nexus operation and immediately report cancellation to
265
+ * the caller. Note that it doesn't guarantee that cancellation is delivered to the operation if
266
+ * calling workflow exits before the delivery is done. If you want to ensure that cancellation is
267
+ * delivered to the operation, use {@link WAIT_CANCELLATION_REQUESTED}.
268
+ *
269
+ * Propagate cancellation request from the Workflow to the Operation, yet _immediately_ report
270
+ * cancellation to the caller, i.e. without waiting for the server to confirm the cancellation
271
+ * request.
272
+ *
273
+ * Note that this cancellation type provides no guarantee, from the Workflow-side, that the
274
+ * cancellation request will be delivered to the Operation Handler. In particular, either the
275
+ * Operation or the Workflow may complete (either successfully or uncessfully) before the
276
+ * cancellation request is delivered, resulting in a situation where the Operation completed
277
+ * successfully, but the Workflow thinks it was cancelled.
278
+ *
279
+ * To guarantee that the Operation will eventually be notified of the cancellation request,
280
+ * use {@link WAIT_CANCELLATION_REQUESTED}.
281
+ */
282
+ TRY_CANCEL: 'TRY_CANCEL',
283
+
284
+ /**
285
+ * Propagate cancellation request from the Workflow to the Operation, then wait for the server
286
+ * to confirm that the Operation cancellation request was delivered to the Operation Handler.
287
+ */
288
+ WAIT_CANCELLATION_REQUESTED: 'WAIT_CANCELLATION_REQUESTED',
289
+
290
+ /**
291
+ * Propagate cancellation request from the Workflow to the Operation, then wait for completion
292
+ * of the Operation.
293
+ */
294
+ WAIT_CANCELLATION_COMPLETED: 'WAIT_CANCELLATION_COMPLETED',
295
+ } as const;
296
+ export type NexusOperationCancellationType =
297
+ (typeof NexusOperationCancellationType)[keyof typeof NexusOperationCancellationType];
298
+
299
+ const [encodeNexusOperationCancellationType, _] = makeProtoEnumConverters<
300
+ coresdk.nexus.NexusOperationCancellationType,
301
+ typeof coresdk.nexus.NexusOperationCancellationType,
302
+ keyof typeof coresdk.nexus.NexusOperationCancellationType,
303
+ typeof NexusOperationCancellationType,
304
+ ''
305
+ >(
306
+ {
307
+ [NexusOperationCancellationType.WAIT_CANCELLATION_COMPLETED]: 0,
308
+ [NexusOperationCancellationType.ABANDON]: 1,
309
+ [NexusOperationCancellationType.TRY_CANCEL]: 2,
310
+ [NexusOperationCancellationType.WAIT_CANCELLATION_REQUESTED]: 3,
311
+ } as const,
312
+ ''
313
+ );
package/src/trigger.ts CHANGED
@@ -10,8 +10,23 @@ import { untrackPromise } from './stack-helpers';
10
10
  * Useful for e.g. waiting for unblocking a Workflow from a Signal.
11
11
  *
12
12
  * @example
13
- * <!--SNIPSTART typescript-trigger-workflow-->
14
- * <!--SNIPEND-->
13
+ * ```ts
14
+ * import { defineSignal, setHandler, sleep, Trigger } from '@temporalio/workflow';
15
+ *
16
+ * const completeUserInteraction = defineSignal('completeUserInteraction');
17
+ *
18
+ * export async function waitOnUser(userId: string): Promise<void> {
19
+ * const userInteraction = new Trigger<boolean>();
20
+ *
21
+ * // programmatically resolve Trigger
22
+ * setHandler(completeUserInteraction, () => userInteraction.resolve(true));
23
+ *
24
+ * const userInteracted = await Promise.race([userInteraction, sleep('30 days')]);
25
+ * if (!userInteracted) {
26
+ * await sendReminderEmail(userId);
27
+ * }
28
+ * }
29
+ * ```
15
30
  */
16
31
  export class Trigger<T> implements PromiseLike<T> {
17
32
  // Typescript does not realize that the promise executor is run synchronously in the constructor