@temporalio/workflow 1.12.0 → 1.12.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.
package/src/workflow.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  ActivityFunction,
3
3
  ActivityOptions,
4
4
  compileRetryPolicy,
5
+ compilePriority,
5
6
  encodeActivityCancellationType,
6
7
  encodeWorkflowIdReusePolicy,
7
8
  extractWorkflowType,
@@ -22,9 +23,9 @@ import {
22
23
  WorkflowReturnType,
23
24
  WorkflowUpdateValidatorType,
24
25
  SearchAttributeUpdatePair,
25
- compilePriority,
26
26
  WorkflowDefinitionOptionsOrGetter,
27
27
  } from '@temporalio/common';
28
+ import { userMetadataToPayload } from '@temporalio/common/lib/user-metadata';
28
29
  import {
29
30
  encodeUnifiedSearchAttributes,
30
31
  searchAttributePayloadConverter,
@@ -33,6 +34,8 @@ import { versioningIntentToProto } from '@temporalio/common/lib/versioning-inten
33
34
  import { Duration, msOptionalToTs, msToNumber, msToTs, requiredTsToMs } from '@temporalio/common/lib/time';
34
35
  import { composeInterceptors } from '@temporalio/common/lib/interceptors';
35
36
  import { temporal } from '@temporalio/proto';
37
+ import { deepMerge } from '@temporalio/common/lib/internal-workflow';
38
+ import { throwIfReservedName } from '@temporalio/common/lib/reserved';
36
39
  import { CancellationScope, registerSleepImplementation } from './cancellation-scope';
37
40
  import { UpdateScope } from './update-scope';
38
41
  import {
@@ -41,6 +44,7 @@ import {
41
44
  SignalWorkflowInput,
42
45
  StartChildWorkflowExecutionInput,
43
46
  TimerInput,
47
+ TimerOptions,
44
48
  } from './interceptors';
45
49
  import {
46
50
  ChildWorkflowCancellationType,
@@ -87,7 +91,7 @@ export function addDefaultWorkflowOptions<T extends Workflow>(
87
91
  /**
88
92
  * Push a startTimer command into state accumulator and register completion
89
93
  */
90
- function timerNextHandler(input: TimerInput) {
94
+ function timerNextHandler({ seq, durationMs, options }: TimerInput) {
91
95
  const activator = getActivator();
92
96
  return new Promise<void>((resolve, reject) => {
93
97
  const scope = CancellationScope.current();
@@ -98,12 +102,12 @@ function timerNextHandler(input: TimerInput) {
98
102
  if (scope.cancellable) {
99
103
  untrackPromise(
100
104
  scope.cancelRequested.catch((err) => {
101
- if (!activator.completions.timer.delete(input.seq)) {
105
+ if (!activator.completions.timer.delete(seq)) {
102
106
  return; // Already resolved or never scheduled
103
107
  }
104
108
  activator.pushCommand({
105
109
  cancelTimer: {
106
- seq: input.seq,
110
+ seq,
107
111
  },
108
112
  });
109
113
  reject(err);
@@ -112,11 +116,12 @@ function timerNextHandler(input: TimerInput) {
112
116
  }
113
117
  activator.pushCommand({
114
118
  startTimer: {
115
- seq: input.seq,
116
- startToFireTimeout: msToTs(input.durationMs),
119
+ seq,
120
+ startToFireTimeout: msToTs(durationMs),
117
121
  },
122
+ userMetadata: userMetadataToPayload(activator.payloadConverter, options?.summary, undefined),
118
123
  });
119
- activator.completions.timer.set(input.seq, {
124
+ activator.completions.timer.set(seq, {
120
125
  resolve,
121
126
  reject,
122
127
  });
@@ -130,8 +135,9 @@ function timerNextHandler(input: TimerInput) {
130
135
  *
131
136
  * @param ms sleep duration - number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}.
132
137
  * If given a negative number or 0, value will be set to 1.
138
+ * @param options optional timer options for additional configuration
133
139
  */
134
- export function sleep(ms: Duration): Promise<void> {
140
+ export function sleep(ms: Duration, options?: TimerOptions): Promise<void> {
135
141
  const activator = assertInWorkflowContext('Workflow.sleep(...) may only be used from a Workflow Execution');
136
142
  const seq = activator.nextSeqs.timer++;
137
143
 
@@ -142,6 +148,7 @@ export function sleep(ms: Duration): Promise<void> {
142
148
  return execute({
143
149
  durationMs,
144
150
  seq,
151
+ options,
145
152
  });
146
153
  }
147
154
 
@@ -198,6 +205,7 @@ function scheduleActivityNextHandler({ options, args, headers, seq, activityType
198
205
  versioningIntent: versioningIntentToProto(options.versioningIntent), // eslint-disable-line deprecation/deprecation
199
206
  priority: options.priority ? compilePriority(options.priority) : undefined,
200
207
  },
208
+ userMetadata: userMetadataToPayload(activator.payloadConverter, options.summary, undefined),
201
209
  });
202
210
  activator.completions.activity.set(seq, {
203
211
  resolve,
@@ -263,6 +271,7 @@ async function scheduleLocalActivityNextHandler({
263
271
  headers,
264
272
  cancellationType: encodeActivityCancellationType(options.cancellationType),
265
273
  },
274
+ userMetadata: userMetadataToPayload(activator.payloadConverter, options.summary, undefined),
266
275
  });
267
276
  activator.completions.activity.set(seq, {
268
277
  resolve,
@@ -399,6 +408,7 @@ function startChildWorkflowExecutionNextHandler({
399
408
  versioningIntent: versioningIntentToProto(options.versioningIntent), // eslint-disable-line deprecation/deprecation
400
409
  priority: options.priority ? compilePriority(options.priority) : undefined,
401
410
  },
411
+ userMetadata: userMetadataToPayload(activator.payloadConverter, options?.staticSummary, options?.staticDetails),
402
412
  });
403
413
  activator.completions.childWorkflowStart.set(seq, {
404
414
  resolve,
@@ -501,7 +511,44 @@ export const NotAnActivityMethod = Symbol.for('__TEMPORAL_NOT_AN_ACTIVITY_METHOD
501
511
  * ```
502
512
  */
503
513
  export type ActivityInterfaceFor<T> = {
504
- [K in keyof T]: T[K] extends ActivityFunction ? T[K] : typeof NotAnActivityMethod;
514
+ [K in keyof T]: T[K] extends ActivityFunction ? ActivityFunctionWithOptions<T[K]> : typeof NotAnActivityMethod;
515
+ };
516
+
517
+ export type ActivityFunctionWithOptions<T extends ActivityFunction> = T & {
518
+ /**
519
+ * Execute the activity, overriding its existing options with the
520
+ * provided options.
521
+ *
522
+ * @param options ActivityOptions
523
+ * @param args: list of arguments
524
+ * @returns return value of the activity
525
+ *
526
+ * @experimental executeWithOptions is a new method to provide call-site options
527
+ * and is subject to change
528
+ */
529
+ executeWithOptions(options: ActivityOptions, args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
530
+ };
531
+
532
+ /**
533
+ * The local activity counterpart to {@link ActivityInterfaceFor}
534
+ */
535
+ export type LocalActivityInterfaceFor<T> = {
536
+ [K in keyof T]: T[K] extends ActivityFunction ? LocalActivityFunctionWithOptions<T[K]> : typeof NotAnActivityMethod;
537
+ };
538
+
539
+ export type LocalActivityFunctionWithOptions<T extends ActivityFunction> = T & {
540
+ /**
541
+ * Run the local activity, overriding its existing options with the
542
+ * provided options.
543
+ *
544
+ * @param options LocalActivityOptions
545
+ * @param args: list of arguments
546
+ * @returns return value of the activity
547
+ *
548
+ * @experimental executeWithOptions is a new method to provide call-site options
549
+ * and is subject to change
550
+ */
551
+ executeWithOptions(options: LocalActivityOptions, args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
505
552
  };
506
553
 
507
554
  /**
@@ -522,6 +569,20 @@ export type ActivityInterfaceFor<T> = {
522
569
  * startToCloseTimeout: '30 minutes',
523
570
  * });
524
571
  *
572
+ * // Use activities with default options
573
+ * const result1 = await httpGet('http://example.com');
574
+ *
575
+ * // Override options for specific activity calls
576
+ * const result2 = await httpGet.executeWithOptions({
577
+ * staticSummary: 'Fetches data from external API',
578
+ * scheduleToCloseTimeout: '5m'
579
+ * }, ['http://api.example.com']);
580
+ *
581
+ * const result3 = await otherActivity.executeWithOptions({
582
+ * staticSummary: 'Processes the fetched data',
583
+ * taskQueue: 'special-task-queue'
584
+ * }, [data]);
585
+ *
525
586
  * // Setup Activities from an explicit interface (e.g. when defined by another SDK)
526
587
  * interface JavaActivities {
527
588
  * httpGetFromJava(url: string): Promise<string>
@@ -538,6 +599,11 @@ export type ActivityInterfaceFor<T> = {
538
599
  *
539
600
  * export function execute(): Promise<void> {
540
601
  * const response = await httpGet("http://example.com");
602
+ * // Or with custom options:
603
+ * const response2 = await httpGetFromJava.executeWithOptions({
604
+ * staticSummary: 'Java HTTP call with timeout override',
605
+ * startToCloseTimeout: '2m'
606
+ * }, ["http://fast-api.example.com"]);
541
607
  * // ...
542
608
  * }
543
609
  * ```
@@ -548,19 +614,27 @@ export function proxyActivities<A = UntypedActivities>(options: ActivityOptions)
548
614
  }
549
615
  // Validate as early as possible for immediate user feedback
550
616
  validateActivityOptions(options);
551
- return new Proxy(
552
- {},
553
- {
554
- get(_, activityType) {
555
- if (typeof activityType !== 'string') {
556
- throw new TypeError(`Only strings are supported for Activity types, got: ${String(activityType)}`);
557
- }
558
- return function activityProxyFunction(...args: unknown[]): Promise<unknown> {
559
- return scheduleActivity(activityType, args, options);
560
- };
561
- },
562
- }
563
- ) as any;
617
+
618
+ return new Proxy({} as ActivityInterfaceFor<A>, {
619
+ get(_, activityType) {
620
+ if (typeof activityType !== 'string') {
621
+ throw new TypeError(`Only strings are supported for Activity types, got: ${String(activityType)}`);
622
+ }
623
+
624
+ function activityProxyFunction(...args: unknown[]): Promise<unknown> {
625
+ return scheduleActivity(activityType as string, args, options);
626
+ }
627
+
628
+ activityProxyFunction.executeWithOptions = function (
629
+ overrideOptions: ActivityOptions,
630
+ args: any[]
631
+ ): Promise<unknown> {
632
+ return scheduleActivity(activityType, args, deepMerge(options, overrideOptions));
633
+ };
634
+
635
+ return activityProxyFunction;
636
+ },
637
+ });
564
638
  }
565
639
 
566
640
  /**
@@ -573,25 +647,35 @@ export function proxyActivities<A = UntypedActivities>(options: ActivityOptions)
573
647
  *
574
648
  * @see {@link proxyActivities} for examples
575
649
  */
576
- export function proxyLocalActivities<A = UntypedActivities>(options: LocalActivityOptions): ActivityInterfaceFor<A> {
650
+ export function proxyLocalActivities<A = UntypedActivities>(
651
+ options: LocalActivityOptions
652
+ ): LocalActivityInterfaceFor<A> {
577
653
  if (options === undefined) {
578
654
  throw new TypeError('options must be defined');
579
655
  }
580
656
  // Validate as early as possible for immediate user feedback
581
657
  validateLocalActivityOptions(options);
582
- return new Proxy(
583
- {},
584
- {
585
- get(_, activityType) {
586
- if (typeof activityType !== 'string') {
587
- throw new TypeError(`Only strings are supported for Activity types, got: ${String(activityType)}`);
588
- }
589
- return function localActivityProxyFunction(...args: unknown[]) {
590
- return scheduleLocalActivity(activityType, args, options);
591
- };
592
- },
593
- }
594
- ) as any;
658
+
659
+ return new Proxy({} as LocalActivityInterfaceFor<A>, {
660
+ get(_, activityType) {
661
+ if (typeof activityType !== 'string') {
662
+ throw new TypeError(`Only strings are supported for Activity types, got: ${String(activityType)}`);
663
+ }
664
+
665
+ function localActivityProxyFunction(...args: unknown[]): Promise<unknown> {
666
+ return scheduleLocalActivity(activityType as string, args, options);
667
+ }
668
+
669
+ localActivityProxyFunction.executeWithOptions = function (
670
+ overrideOptions: LocalActivityOptions,
671
+ args: any[]
672
+ ): Promise<unknown> {
673
+ return scheduleLocalActivity(activityType, args, deepMerge(options, overrideOptions));
674
+ };
675
+
676
+ return localActivityProxyFunction;
677
+ },
678
+ });
595
679
  }
596
680
 
597
681
  // TODO: deprecate this patch after "enough" time has passed
@@ -961,13 +1045,13 @@ export function makeContinueAsNewFunc<F extends Workflow>(
961
1045
  * @example
962
1046
  *
963
1047
  * ```ts
964
- *import { continueAsNew } from '@temporalio/workflow';
965
- import { SearchAttributeType } from '@temporalio/common';
1048
+ * import { continueAsNew } from '@temporalio/workflow';
1049
+ * import { SearchAttributeType } from '@temporalio/common';
966
1050
  *
967
- *export async function myWorkflow(n: number): Promise<void> {
968
- * // ... Workflow logic
969
- * await continueAsNew<typeof myWorkflow>(n + 1);
970
- *}
1051
+ * export async function myWorkflow(n: number): Promise<void> {
1052
+ * // ... Workflow logic
1053
+ * await continueAsNew<typeof myWorkflow>(n + 1);
1054
+ * }
971
1055
  * ```
972
1056
  */
973
1057
  export function continueAsNew<F extends Workflow>(...args: Parameters<F>): Promise<never> {
@@ -1049,6 +1133,18 @@ export function deprecatePatch(patchId: string): void {
1049
1133
  activator.patchInternal(patchId, true);
1050
1134
  }
1051
1135
 
1136
+ /**
1137
+ * Returns a Promise that resolves when `fn` evaluates to `true` or `timeout` expires, providing
1138
+ * options to configure the timer (i.e. provide metadata)
1139
+ *
1140
+ * @param timeout number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}
1141
+ *
1142
+ * @returns a boolean indicating whether the condition was true before the timeout expires
1143
+ *
1144
+ * @experimental TimerOptions is a new addition and subject to change
1145
+ */
1146
+ export function condition(fn: () => boolean, timeout: Duration, options: TimerOptions): Promise<boolean>;
1147
+
1052
1148
  /**
1053
1149
  * Returns a Promise that resolves when `fn` evaluates to `true` or `timeout` expires.
1054
1150
  *
@@ -1063,7 +1159,7 @@ export function condition(fn: () => boolean, timeout: Duration): Promise<boolean
1063
1159
  */
1064
1160
  export function condition(fn: () => boolean): Promise<void>;
1065
1161
 
1066
- export async function condition(fn: () => boolean, timeout?: Duration): Promise<void | boolean> {
1162
+ export async function condition(fn: () => boolean, timeout?: Duration, opts?: TimerOptions): Promise<void | boolean> {
1067
1163
  assertInWorkflowContext('Workflow.condition(...) may only be used from a Workflow Execution.');
1068
1164
  // Prior to 1.5.0, `condition(fn, 0)` was treated as equivalent to `condition(fn, undefined)`
1069
1165
  if (timeout === 0 && !patched(CONDITION_0_PATCH)) {
@@ -1072,7 +1168,7 @@ export async function condition(fn: () => boolean, timeout?: Duration): Promise<
1072
1168
  if (typeof timeout === 'number' || typeof timeout === 'string') {
1073
1169
  return CancellationScope.cancellable(async () => {
1074
1170
  try {
1075
- return await Promise.race([sleep(timeout).then(() => false), conditionInner(fn).then(() => true)]);
1171
+ return await Promise.race([sleep(timeout, opts).then(() => false), conditionInner(fn).then(() => true)]);
1076
1172
  } finally {
1077
1173
  CancellationScope.current().cancel();
1078
1174
  }
@@ -1272,6 +1368,8 @@ export function setHandler<
1272
1368
  options?: QueryHandlerOptions | SignalHandlerOptions | UpdateHandlerOptions<Args>
1273
1369
  ): void {
1274
1370
  const activator = assertInWorkflowContext('Workflow.setHandler(...) may only be used from a Workflow Execution.');
1371
+ // Cannot register handler for reserved names
1372
+ throwIfReservedName(def.type, def.name);
1275
1373
  const description = options?.description;
1276
1374
  if (def.type === 'update') {
1277
1375
  if (typeof handler === 'function') {
@@ -1655,3 +1753,13 @@ export function setWorkflowOptions<A extends any[], RT>(
1655
1753
  export const stackTraceQuery = defineQuery<string>('__stack_trace');
1656
1754
  export const enhancedStackTraceQuery = defineQuery<EnhancedStackTrace>('__enhanced_stack_trace');
1657
1755
  export const workflowMetadataQuery = defineQuery<temporal.api.sdk.v1.IWorkflowMetadata>('__temporal_workflow_metadata');
1756
+
1757
+ export function getCurrentDetails(): string {
1758
+ const activator = assertInWorkflowContext('getCurrentDetails() may only be used from a Workflow Execution.');
1759
+ return activator.currentDetails;
1760
+ }
1761
+
1762
+ export function setCurrentDetails(details: string): void {
1763
+ const activator = assertInWorkflowContext('getCurrentDetails() may only be used from a Workflow Execution.');
1764
+ activator.currentDetails = details;
1765
+ }