@temporalio/workflow 1.17.2 → 1.17.3

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/internals.ts CHANGED
@@ -1,17 +1,20 @@
1
+ import type { AsyncLocalStorage as ALS } from 'node:async_hooks';
1
2
  import type { RawSourceMap } from 'source-map';
2
3
  import type {
4
+ ActivitySerializationContext,
3
5
  FailureConverter,
4
6
  PayloadConverter,
7
+ ProtoFailure,
5
8
  Workflow,
9
+ WorkflowFunctionWithOptions,
6
10
  WorkflowQueryAnnotatedType,
7
11
  WorkflowSignalAnnotatedType,
8
12
  WorkflowUpdateAnnotatedType,
9
- ProtoFailure,
10
13
  WorkflowUpdateType,
11
14
  WorkflowUpdateValidatorType,
12
- WorkflowFunctionWithOptions,
13
15
  VersioningBehavior,
14
16
  WorkflowDefinitionOptions,
17
+ WorkflowSerializationContext,
15
18
  } from '@temporalio/common';
16
19
  import {
17
20
  defaultFailureConverter,
@@ -31,7 +34,6 @@ import {
31
34
  decodeSearchAttributes,
32
35
  decodeTypedSearchAttributes,
33
36
  } from '@temporalio/common/lib/converter/payload-search-attributes';
34
- import { composeInterceptors } from '@temporalio/common/lib/interceptors';
35
37
  import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
36
38
  import type { coresdk, temporal } from '@temporalio/proto';
37
39
  import {
@@ -42,7 +44,9 @@ import {
42
44
  import type { RNG } from './alea';
43
45
  import { alea } from './alea';
44
46
  import { RootCancellationScope } from './cancellation-scope';
45
- import { UpdateScope } from './update-scope';
47
+ import { composeInterceptors } from './interceptor-composition';
48
+ import { AsyncLocalStorage, UpdateScope } from './update-scope';
49
+ import { deriveAleaSeed } from './random-stream-seed';
46
50
  import { DeterminismViolationError, LocalActivityDoBackoff, isCancellation } from './errors';
47
51
  import type {
48
52
  QueryInput,
@@ -106,9 +110,10 @@ export interface PromiseStackStore {
106
110
  promiseToStack: Map<Promise<unknown>, Stack>;
107
111
  }
108
112
 
109
- export interface Completion<Success> {
113
+ export interface Completion<Success, Context = never> {
110
114
  resolve(val: Success): void;
111
115
  reject(reason: Error): void;
116
+ context?: Context;
112
117
  }
113
118
 
114
119
  export interface Condition {
@@ -139,6 +144,10 @@ interface MessageHandlerExecution {
139
144
 
140
145
  type InferMapValue<T> = T extends Map<number, infer V> ? V : never;
141
146
 
147
+ interface ScopedWorkflowRandomSource {
148
+ random(): number;
149
+ }
150
+
142
151
  /**
143
152
  * Keeps all of the Workflow runtime state like pending completions for activities and timers.
144
153
  *
@@ -168,13 +177,13 @@ export class Activator implements ActivationHandler {
168
177
  */
169
178
  readonly completions = {
170
179
  timer: new Map<number, Completion<void>>(),
171
- activity: new Map<number, Completion<unknown>>(),
180
+ activity: new Map<number, Completion<unknown, ActivitySerializationContext>>(),
172
181
  nexusOperationStart: new Map<number, Completion<StartNexusOperationOutput>>(),
173
182
  nexusOperationComplete: new Map<number, Completion<unknown>>(),
174
- childWorkflowStart: new Map<number, Completion<string>>(),
175
- childWorkflowComplete: new Map<number, Completion<unknown>>(),
176
- signalWorkflow: new Map<number, Completion<void>>(),
177
- cancelWorkflow: new Map<number, Completion<void>>(),
183
+ childWorkflowStart: new Map<number, Completion<string, WorkflowSerializationContext>>(),
184
+ childWorkflowComplete: new Map<number, Completion<unknown, WorkflowSerializationContext>>(),
185
+ signalWorkflow: new Map<number, Completion<void, WorkflowSerializationContext>>(),
186
+ cancelWorkflow: new Map<number, Completion<void, WorkflowSerializationContext>>(),
178
187
  };
179
188
 
180
189
  /**
@@ -268,6 +277,22 @@ export class Activator implements ActivationHandler {
268
277
  */
269
278
  public workflowTaskError: unknown;
270
279
 
280
+ /**
281
+ * Error type _names_ (from {@link WorkerOptions.workflowFailureErrorTypes}) that
282
+ * should cause Workflow Execution failure rather than WFT failure.
283
+ *
284
+ * Set at workflow creation time from the worker options.
285
+ */
286
+ public failureExceptionTypeNames: string[] = [];
287
+
288
+ /**
289
+ * Error _types_ (from {@link WorkflowDefinitionOptions.failureExceptionTypes})
290
+ * that should cause Workflow Execution failure rather than WFT failure.
291
+ *
292
+ * Set in `worker-interface.ts` after the workflow definition options are read.
293
+ */
294
+ public workflowDefinitionFailureExceptionTypes: Array<new (...args: any[]) => Error> | undefined = undefined;
295
+
271
296
  /**
272
297
  * Set to true when running synchronous code (e.g. while processing activation jobs and when calling
273
298
  * `tryUnblockConditions()`). While this flag is set, it is safe to let errors bubble up.
@@ -419,10 +444,24 @@ export class Activator implements ActivationHandler {
419
444
  public info: WorkflowInfo;
420
445
 
421
446
  /**
422
- * A deterministic RNG, used by the isolate's overridden Math.random
447
+ * The main deterministic RNG for this workflow execution.
448
+ *
449
+ * Scoped overrides used by `WorkflowRandomStream.with(...)` are layered on top of this RNG.
423
450
  */
424
451
  public random: RNG;
425
452
 
453
+ /**
454
+ * The current seed material for this workflow execution's deterministic RNGs.
455
+ */
456
+ public randomnessSeed: number[];
457
+
458
+ /**
459
+ * Additional deterministic RNG streams keyed by stable stream name.
460
+ */
461
+ public readonly namedRandomStreams = new Map<string, RNG>();
462
+
463
+ protected currentRandomStorage?: ALS<ScopedWorkflowRandomSource | undefined>;
464
+
426
465
  public payloadConverter: PayloadConverter = defaultPayloadConverter;
427
466
  public failureConverter: FailureConverter = defaultFailureConverter;
428
467
 
@@ -472,15 +511,55 @@ export class Activator implements ActivationHandler {
472
511
  randomnessSeed,
473
512
  registeredActivityNames,
474
513
  stackTracesEnabled,
514
+ failureExceptionTypeNames,
475
515
  }: WorkflowCreateOptionsInternal) {
476
516
  this.getTimeOfDay = getTimeOfDay;
477
517
  this.info = info;
478
518
  this.now = now;
479
519
  this.showStackTraceSources = showStackTraceSources;
480
520
  this.sourceMap = sourceMap;
481
- this.random = alea(randomnessSeed);
521
+ this.randomnessSeed = [...randomnessSeed];
522
+ this.random = alea(this.randomnessSeed);
482
523
  this.registeredActivityNames = registeredActivityNames;
483
524
  this.stackTracesEnabled = stackTracesEnabled;
525
+ this.failureExceptionTypeNames = failureExceptionTypeNames ?? [];
526
+ }
527
+
528
+ protected setRandomnessSeed(randomnessSeed: number[]): void {
529
+ this.randomnessSeed = [...randomnessSeed];
530
+ this.random = alea(this.randomnessSeed);
531
+ this.namedRandomStreams.clear();
532
+ }
533
+
534
+ public getNamedRandom(name: string): RNG {
535
+ const cached = this.namedRandomStreams.get(name);
536
+ if (cached !== undefined) {
537
+ return cached;
538
+ }
539
+
540
+ const random = alea(deriveAleaSeed(this.randomnessSeed, name));
541
+ this.namedRandomStreams.set(name, random);
542
+ return random;
543
+ }
544
+
545
+ protected withRandomSource<T>(randomSource: ScopedWorkflowRandomSource | undefined, fn: () => T): T {
546
+ return (this.currentRandomStorage ??= new AsyncLocalStorage<ScopedWorkflowRandomSource | undefined>()).run(
547
+ randomSource,
548
+ fn
549
+ );
550
+ }
551
+
552
+ public withCurrentRandom<T>(randomSource: ScopedWorkflowRandomSource, fn: () => T): T {
553
+ return this.withRandomSource(randomSource, fn);
554
+ }
555
+
556
+ public bindCurrentRandom<T extends (...args: any[]) => any>(fn: T): T {
557
+ const randomSource = this.currentRandomStorage?.getStore();
558
+ return ((...args: Parameters<T>) => this.withRandomSource(randomSource, () => fn(...args))) as T;
559
+ }
560
+
561
+ public currentRandom(): number {
562
+ return this.currentRandomStorage?.getStore()?.random() ?? this.random();
484
563
  }
485
564
 
486
565
  /**
@@ -554,12 +633,13 @@ export class Activator implements ActivationHandler {
554
633
 
555
634
  public startWorkflow(activation: coresdk.workflow_activation.IInitializeWorkflow): void {
556
635
  const execute = composeInterceptors(this.interceptors.inbound, 'execute', this.startWorkflowNextHandler.bind(this));
636
+ const context = this.workflowSerializationContext();
557
637
 
558
638
  untrackPromise(
559
639
  executeWithLifecycleLogging(() =>
560
640
  execute({
561
641
  headers: activation.headers ?? {},
562
- args: arrayFromPayloads(this.payloadConverter, activation.arguments),
642
+ args: arrayFromPayloads(this.payloadConverter, activation.arguments, context),
563
643
  })
564
644
  ).then(this.completeWorkflow.bind(this), this.handleWorkflowFailure.bind(this))
565
645
  );
@@ -567,6 +647,7 @@ export class Activator implements ActivationHandler {
567
647
 
568
648
  public initializeWorkflow(activation: coresdk.workflow_activation.IInitializeWorkflow): void {
569
649
  const { continuedFailure, lastCompletionResult, memo, searchAttributes } = activation;
650
+ const context = this.workflowSerializationContext();
570
651
 
571
652
  // Most things related to initialization have already been handled in the constructor
572
653
  this.mutateWorkflowInfo((info) => ({
@@ -575,15 +656,17 @@ export class Activator implements ActivationHandler {
575
656
  searchAttributes: decodeSearchAttributes(searchAttributes?.indexedFields),
576
657
  typedSearchAttributes: decodeTypedSearchAttributes(searchAttributes?.indexedFields),
577
658
 
578
- memo: mapFromPayloads(this.payloadConverter, memo?.fields),
579
- lastResult: fromPayloadsAtIndex(this.payloadConverter, 0, lastCompletionResult?.payloads),
659
+ memo: mapFromPayloads(this.payloadConverter, memo?.fields, context),
660
+ lastResult: fromPayloadsAtIndex(this.payloadConverter, 0, lastCompletionResult?.payloads, context),
580
661
  lastFailure:
581
662
  continuedFailure != null
582
- ? this.failureConverter.failureToError(continuedFailure, this.payloadConverter)
663
+ ? this.failureConverter.failureToError(continuedFailure, this.payloadConverter, context)
583
664
  : undefined,
584
665
  }));
585
- if (this.workflowDefinitionOptionsGetter) {
586
- this.versioningBehavior = this.workflowDefinitionOptionsGetter().versioningBehavior;
666
+ const workflowDefinitionOpts = this.workflowDefinitionOptionsGetter?.();
667
+ if (workflowDefinitionOpts) {
668
+ this.versioningBehavior = workflowDefinitionOpts.versioningBehavior;
669
+ this.workflowDefinitionFailureExceptionTypes = workflowDefinitionOpts.failureExceptionTypes;
587
670
  }
588
671
  }
589
672
 
@@ -603,23 +686,23 @@ export class Activator implements ActivationHandler {
603
686
  if (!activation.result) {
604
687
  throw new TypeError('Got ResolveActivity activation with no result');
605
688
  }
606
- const { resolve, reject } = this.consumeCompletion('activity', getSeq(activation));
689
+ const { resolve, reject, context } = this.consumeCompletion('activity', getSeq(activation));
607
690
  if (activation.result.completed) {
608
691
  const completed = activation.result.completed;
609
- const result = completed.result ? this.payloadConverter.fromPayload(completed.result) : undefined;
692
+ const result = completed.result ? this.payloadConverter.fromPayload(completed.result, context) : undefined;
610
693
  resolve(result);
611
694
  } else if (activation.result.failed) {
612
695
  const { failure } = activation.result.failed;
613
696
  if (failure == null) {
614
697
  throw new TypeError('Got failed result with no failure attribute');
615
698
  }
616
- reject(this.failureToError(failure));
699
+ reject(this.failureConverter.failureToError(failure, this.payloadConverter, context));
617
700
  } else if (activation.result.cancelled) {
618
701
  const { failure } = activation.result.cancelled;
619
702
  if (failure == null) {
620
703
  throw new TypeError('Got cancelled result with no failure attribute');
621
704
  }
622
- reject(this.failureToError(failure));
705
+ reject(this.failureConverter.failureToError(failure, this.payloadConverter, context));
623
706
  } else if (activation.result.backoff) {
624
707
  reject(new LocalActivityDoBackoff(activation.result.backoff));
625
708
  }
@@ -628,7 +711,7 @@ export class Activator implements ActivationHandler {
628
711
  public resolveChildWorkflowExecutionStart(
629
712
  activation: coresdk.workflow_activation.IResolveChildWorkflowExecutionStart
630
713
  ): void {
631
- const { resolve, reject } = this.consumeCompletion('childWorkflowStart', getSeq(activation));
714
+ const { resolve, reject, context } = this.consumeCompletion('childWorkflowStart', getSeq(activation));
632
715
  if (activation.succeeded) {
633
716
  if (!activation.succeeded.runId) {
634
717
  throw new TypeError('Got ResolveChildWorkflowExecutionStart with no runId');
@@ -652,7 +735,7 @@ export class Activator implements ActivationHandler {
652
735
  if (!activation.cancelled.failure) {
653
736
  throw new TypeError('Got no failure in cancelled variant');
654
737
  }
655
- reject(this.failureToError(activation.cancelled.failure));
738
+ reject(this.failureConverter.failureToError(activation.cancelled.failure, this.payloadConverter, context));
656
739
  } else {
657
740
  throw new TypeError('Got ResolveChildWorkflowExecutionStart with no status');
658
741
  }
@@ -662,23 +745,23 @@ export class Activator implements ActivationHandler {
662
745
  if (!activation.result) {
663
746
  throw new TypeError('Got ResolveChildWorkflowExecution activation with no result');
664
747
  }
665
- const { resolve, reject } = this.consumeCompletion('childWorkflowComplete', getSeq(activation));
748
+ const { resolve, reject, context } = this.consumeCompletion('childWorkflowComplete', getSeq(activation));
666
749
  if (activation.result.completed) {
667
750
  const completed = activation.result.completed;
668
- const result = completed.result ? this.payloadConverter.fromPayload(completed.result) : undefined;
751
+ const result = completed.result ? this.payloadConverter.fromPayload(completed.result, context) : undefined;
669
752
  resolve(result);
670
753
  } else if (activation.result.failed) {
671
754
  const { failure } = activation.result.failed;
672
755
  if (failure == null) {
673
756
  throw new TypeError('Got failed result with no failure attribute');
674
757
  }
675
- reject(this.failureToError(failure));
758
+ reject(this.failureConverter.failureToError(failure, this.payloadConverter, context));
676
759
  } else if (activation.result.cancelled) {
677
760
  const { failure } = activation.result.cancelled;
678
761
  if (failure == null) {
679
762
  throw new TypeError('Got cancelled result with no failure attribute');
680
763
  }
681
- reject(this.failureToError(failure));
764
+ reject(this.failureConverter.failureToError(failure, this.payloadConverter, context));
682
765
  }
683
766
  }
684
767
 
@@ -704,9 +787,10 @@ export class Activator implements ActivationHandler {
704
787
 
705
788
  public resolveNexusOperation(activation: coresdk.workflow_activation.IResolveNexusOperation): void {
706
789
  const seq = getSeq(activation);
790
+ const context = this.workflowSerializationContext();
707
791
 
708
792
  if (activation.result?.completed) {
709
- const result = this.payloadConverter.fromPayload(activation.result.completed);
793
+ const result = this.payloadConverter.fromPayload(activation.result.completed, context);
710
794
 
711
795
  // It is possible for ResolveNexusOperation to be received without a prior ResolveNexusOperationStart,
712
796
  // e.g. because the handler completed the Operation synchronously.
@@ -767,8 +851,14 @@ export class Activator implements ActivationHandler {
767
851
  throw new TypeError('Missing query activation attributes');
768
852
  }
769
853
 
770
- // If query has __temporal_ prefix but no handler exists, throw error
771
- if (queryType.startsWith(TEMPORAL_RESERVED_PREFIX) && !this.queryHandlers.has(queryType)) {
854
+ // Reject __temporal_-prefixed queries that would otherwise be routed to the
855
+ // user's default handler. A specific registered handler (e.g. from a
856
+ // contrib package) is allowed through.
857
+ if (
858
+ queryType.startsWith(TEMPORAL_RESERVED_PREFIX) &&
859
+ !this.queryHandlers.has(queryType) &&
860
+ this.defaultQueryHandler !== undefined
861
+ ) {
772
862
  throw new TypeError(`Cannot use query name: '${queryType}', with reserved prefix: '${TEMPORAL_RESERVED_PREFIX}'`);
773
863
  }
774
864
 
@@ -779,9 +869,10 @@ export class Activator implements ActivationHandler {
779
869
  queryType === ENHANCED_STACK_TRACE_QUERY_NAME;
780
870
  const interceptors = isInternalQuery ? [] : this.interceptors.inbound;
781
871
  const execute = composeInterceptors(interceptors, 'handleQuery', this.queryWorkflowNextHandler.bind(this));
872
+ const context = this.workflowSerializationContext();
782
873
  execute({
783
874
  queryName: queryType,
784
- args: arrayFromPayloads(this.payloadConverter, activation.arguments),
875
+ args: arrayFromPayloads(this.payloadConverter, activation.arguments, context),
785
876
  queryId,
786
877
  headers: headers ?? {},
787
878
  }).then(
@@ -802,8 +893,15 @@ export class Activator implements ActivationHandler {
802
893
  throw new TypeError('Missing activation update protocolInstanceId');
803
894
  }
804
895
 
805
- // If update has __temporal_ prefix but no handler exists, throw error
806
- if (name.startsWith(TEMPORAL_RESERVED_PREFIX) && !this.updateHandlers.get(name)) {
896
+ // Reject __temporal_-prefixed updates that would otherwise be routed to the
897
+ // user's default handler. A specific registered handler (e.g. from a
898
+ // contrib package) is allowed through, and unregistered names without a
899
+ // default handler fall through to the buffer-then-reject path below.
900
+ if (
901
+ name.startsWith(TEMPORAL_RESERVED_PREFIX) &&
902
+ !this.updateHandlers.has(name) &&
903
+ this.defaultUpdateHandler !== undefined
904
+ ) {
807
905
  throw new TypeError(`Cannot use update name: '${name}', with reserved prefix: '${TEMPORAL_RESERVED_PREFIX}'`);
808
906
  }
809
907
 
@@ -831,12 +929,15 @@ export class Activator implements ActivationHandler {
831
929
  return;
832
930
  }
833
931
 
834
- const makeInput = (): UpdateInput => ({
835
- updateId,
836
- args: arrayFromPayloads(this.payloadConverter, activation.input),
837
- name,
838
- headers: headers ?? {},
839
- });
932
+ const makeInput = (): UpdateInput => {
933
+ const context = this.workflowSerializationContext();
934
+ return {
935
+ updateId,
936
+ args: arrayFromPayloads(this.payloadConverter, activation.input, context),
937
+ name,
938
+ headers: headers ?? {},
939
+ };
940
+ };
840
941
 
841
942
  // The implementation below is responsible for upholding, and constrained
842
943
  // by, the following contract:
@@ -979,8 +1080,15 @@ export class Activator implements ActivationHandler {
979
1080
  throw new TypeError('Missing activation signalName');
980
1081
  }
981
1082
 
982
- // If signal has __temporal_ prefix but no handler exists, throw error
983
- if (signalName.startsWith(TEMPORAL_RESERVED_PREFIX) && !this.signalHandlers.has(signalName)) {
1083
+ // Reject __temporal_-prefixed signals that would otherwise be routed to the
1084
+ // user's default handler. A specific registered handler (e.g. from a
1085
+ // contrib package) is allowed through, and unregistered names without a
1086
+ // default handler fall through to the buffer-then-reject path below.
1087
+ if (
1088
+ signalName.startsWith(TEMPORAL_RESERVED_PREFIX) &&
1089
+ !this.signalHandlers.has(signalName) &&
1090
+ this.defaultSignalHandler !== undefined
1091
+ ) {
984
1092
  throw new TypeError(
985
1093
  `Cannot use signal name: '${signalName}', with reserved prefix: '${TEMPORAL_RESERVED_PREFIX}'`
986
1094
  );
@@ -1007,8 +1115,9 @@ export class Activator implements ActivationHandler {
1007
1115
  const signalExecutionNum = this.signalHandlerExecutionSeq++;
1008
1116
  this.inProgressSignals.set(signalExecutionNum, { name: signalName, unfinishedPolicy });
1009
1117
  const execute = composeInterceptors(interceptors, 'handleSignal', this.signalWorkflowNextHandler.bind(this));
1118
+ const context = this.workflowSerializationContext();
1010
1119
  execute({
1011
- args: arrayFromPayloads(this.payloadConverter, activation.input),
1120
+ args: arrayFromPayloads(this.payloadConverter, activation.input, context),
1012
1121
  signalName,
1013
1122
  headers: headers ?? {},
1014
1123
  })
@@ -1033,9 +1142,9 @@ export class Activator implements ActivationHandler {
1033
1142
  }
1034
1143
 
1035
1144
  public resolveSignalExternalWorkflow(activation: coresdk.workflow_activation.IResolveSignalExternalWorkflow): void {
1036
- const { resolve, reject } = this.consumeCompletion('signalWorkflow', getSeq(activation));
1145
+ const { resolve, reject, context } = this.consumeCompletion('signalWorkflow', getSeq(activation));
1037
1146
  if (activation.failure) {
1038
- reject(this.failureToError(activation.failure));
1147
+ reject(this.failureConverter.failureToError(activation.failure, this.payloadConverter, context));
1039
1148
  } else {
1040
1149
  resolve(undefined);
1041
1150
  }
@@ -1044,9 +1153,9 @@ export class Activator implements ActivationHandler {
1044
1153
  public resolveRequestCancelExternalWorkflow(
1045
1154
  activation: coresdk.workflow_activation.IResolveRequestCancelExternalWorkflow
1046
1155
  ): void {
1047
- const { resolve, reject } = this.consumeCompletion('cancelWorkflow', getSeq(activation));
1156
+ const { resolve, reject, context } = this.consumeCompletion('cancelWorkflow', getSeq(activation));
1048
1157
  if (activation.failure) {
1049
- reject(this.failureToError(activation.failure));
1158
+ reject(this.failureConverter.failureToError(activation.failure, this.payloadConverter, context));
1050
1159
  } else {
1051
1160
  resolve(undefined);
1052
1161
  }
@@ -1076,7 +1185,7 @@ export class Activator implements ActivationHandler {
1076
1185
  if (!activation.randomnessSeed) {
1077
1186
  throw new TypeError('Expected activation with randomnessSeed attribute');
1078
1187
  }
1079
- this.random = alea(activation.randomnessSeed.toBytes());
1188
+ this.setRandomnessSeed(activation.randomnessSeed.toBytes());
1080
1189
  }
1081
1190
 
1082
1191
  public notifyHasPatch(activation: coresdk.workflow_activation.INotifyHasPatch): void {
@@ -1171,7 +1280,7 @@ export class Activator implements ActivationHandler {
1171
1280
  this.pushCommand({ cancelWorkflowExecution: {} }, true);
1172
1281
  } else if (error instanceof ContinueAsNew) {
1173
1282
  this.pushCommand({ continueAsNewWorkflowExecution: error.command }, true);
1174
- } else if (error instanceof TemporalFailure) {
1283
+ } else if (error instanceof TemporalFailure || this.isConfiguredFailureException(error)) {
1175
1284
  // Fail the workflow. We do not want to issue unfinishedHandlers warnings. To achieve that, we
1176
1285
  // mark all handlers as completed now.
1177
1286
  this.inProgressSignals.clear();
@@ -1179,7 +1288,7 @@ export class Activator implements ActivationHandler {
1179
1288
  this.pushCommand(
1180
1289
  {
1181
1290
  failWorkflowExecution: {
1182
- failure: this.errorToFailure(error),
1291
+ failure: this.errorToFailure(ensureTemporalFailure(error)),
1183
1292
  },
1184
1293
  },
1185
1294
  true
@@ -1189,6 +1298,42 @@ export class Activator implements ActivationHandler {
1189
1298
  }
1190
1299
  }
1191
1300
 
1301
+ /**
1302
+ * Returns true if the given error matches any of the configured failure exception types
1303
+ * (from {@link WorkerOptions.workflowFailureErrorTypes} or
1304
+ * {@link WorkflowDefinitionOptions.failureExceptionTypes}).
1305
+ */
1306
+ private isConfiguredFailureException(error: unknown): boolean {
1307
+ // Check class references from WorkflowDefinitionOptions (instanceof-based, supports subclasses)
1308
+ if (this.workflowDefinitionFailureExceptionTypes) {
1309
+ // We guarantee that including Error in the list will catch _any_ error.
1310
+ if (this.workflowDefinitionFailureExceptionTypes.includes(Error)) return true;
1311
+
1312
+ for (const errorType of this.workflowDefinitionFailureExceptionTypes) {
1313
+ if (error instanceof errorType) return true;
1314
+ }
1315
+ }
1316
+
1317
+ // Check class name strings from WorkerOptions (prototype-chain-based)
1318
+ if (this.failureExceptionTypeNames.length > 0) {
1319
+ // We guarantee that including 'Error' in the list will catch _any_ error.
1320
+ if (this.failureExceptionTypeNames.includes('Error')) return true;
1321
+
1322
+ if (typeof error === 'object' && error !== null) {
1323
+ let ctor = (error as any).constructor;
1324
+ while (ctor != null && ctor !== Function.prototype) {
1325
+ const name = (ctor as any).name as string | undefined;
1326
+ if (name) {
1327
+ if (this.failureExceptionTypeNames.includes(name)) return true;
1328
+ }
1329
+ ctor = Object.getPrototypeOf(ctor);
1330
+ }
1331
+ }
1332
+ }
1333
+
1334
+ return false;
1335
+ }
1336
+
1192
1337
  recordWorkflowTaskError(error: unknown): void {
1193
1338
  // Only keep the first error that bubbles up; subsequent errors will be ignored.
1194
1339
  if (this.workflowTaskError === undefined) this.workflowTaskError = error;
@@ -1208,8 +1353,9 @@ export class Activator implements ActivationHandler {
1208
1353
  }
1209
1354
 
1210
1355
  private completeQuery(queryId: string, result: unknown): void {
1356
+ const context = this.workflowSerializationContext();
1211
1357
  this.pushCommand({
1212
- respondToQuery: { queryId, succeeded: { response: this.payloadConverter.toPayload(result) } },
1358
+ respondToQuery: { queryId, succeeded: { response: this.payloadConverter.toPayload(result, context) } },
1213
1359
  });
1214
1360
  }
1215
1361
 
@@ -1227,8 +1373,9 @@ export class Activator implements ActivationHandler {
1227
1373
  }
1228
1374
 
1229
1375
  private completeUpdate(protocolInstanceId: string, result: unknown): void {
1376
+ const context = this.workflowSerializationContext();
1230
1377
  this.pushCommand({
1231
- updateResponse: { protocolInstanceId, completed: this.payloadConverter.toPayload(result) },
1378
+ updateResponse: { protocolInstanceId, completed: this.payloadConverter.toPayload(result, context) },
1232
1379
  });
1233
1380
  }
1234
1381
 
@@ -1266,10 +1413,11 @@ export class Activator implements ActivationHandler {
1266
1413
  }
1267
1414
 
1268
1415
  private completeWorkflow(result: unknown): void {
1416
+ const context = this.workflowSerializationContext();
1269
1417
  this.pushCommand(
1270
1418
  {
1271
1419
  completeWorkflowExecution: {
1272
- result: this.payloadConverter.toPayload(result),
1420
+ result: this.payloadConverter.toPayload(result, context),
1273
1421
  },
1274
1422
  },
1275
1423
  true
@@ -1277,11 +1425,21 @@ export class Activator implements ActivationHandler {
1277
1425
  }
1278
1426
 
1279
1427
  errorToFailure(err: unknown): ProtoFailure {
1280
- return this.failureConverter.errorToFailure(err, this.payloadConverter);
1428
+ const context = this.workflowSerializationContext();
1429
+ return this.failureConverter.errorToFailure(err, this.payloadConverter, context);
1281
1430
  }
1282
1431
 
1283
1432
  failureToError(failure: ProtoFailure): Error {
1284
- return this.failureConverter.failureToError(failure, this.payloadConverter);
1433
+ const context = this.workflowSerializationContext();
1434
+ return this.failureConverter.failureToError(failure, this.payloadConverter, context);
1435
+ }
1436
+
1437
+ private workflowSerializationContext(): WorkflowSerializationContext {
1438
+ return {
1439
+ type: 'workflow',
1440
+ namespace: this.info.namespace,
1441
+ workflowId: this.info.workflowId,
1442
+ };
1285
1443
  }
1286
1444
  }
1287
1445
 
package/src/logs.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { composeInterceptors } from '@temporalio/common/lib/interceptors';
2
1
  import { SdkComponent } from '@temporalio/common';
2
+ import { composeInterceptors } from './interceptor-composition';
3
3
  import { untrackPromise } from './stack-helpers';
4
4
  import { type Sink, type Sinks, proxySinks } from './sinks';
5
5
  import { isCancellation } from './errors';
package/src/metrics.ts CHANGED
@@ -7,7 +7,7 @@ import type {
7
7
  NumericMetricValueType,
8
8
  } from '@temporalio/common';
9
9
  import { MetricMeterWithComposedTags } from '@temporalio/common';
10
- import { composeInterceptors } from '@temporalio/common/lib/interceptors';
10
+ import { composeInterceptors } from './interceptor-composition';
11
11
  import type { Sink, Sinks } from './sinks';
12
12
  import { proxySinks } from './sinks';
13
13
  import { workflowInfo } from './workflow';
package/src/nexus.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type * 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
- import { composeInterceptors } from '@temporalio/common/lib/interceptors';
5
4
  import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow/enums-helpers';
6
5
  import type { coresdk } from '@temporalio/proto';
7
6
  import { CancellationScope } from './cancellation-scope';
8
7
  import { getActivator } from './global-attributes';
8
+ import { composeInterceptors } from './interceptor-composition';
9
9
  import { untrackPromise } from './stack-helpers';
10
10
  import type { StartNexusOperationInput, StartNexusOperationOutput, StartNexusOperationOptions } from './interceptors';
11
11
 
@@ -191,6 +191,11 @@ function startNexusOperationNextHandler({
191
191
  headers,
192
192
  }: StartNexusOperationInput): Promise<StartNexusOperationOutput> {
193
193
  const activator = getActivator();
194
+ const context = {
195
+ type: 'workflow' as const,
196
+ namespace: activator.info.namespace,
197
+ workflowId: activator.info.workflowId,
198
+ };
194
199
 
195
200
  return new Promise<StartNexusOperationOutput>((resolve, reject) => {
196
201
  const scope = CancellationScope.current();
@@ -223,13 +228,13 @@ function startNexusOperationNextHandler({
223
228
  service,
224
229
  operation,
225
230
  nexusHeader: headers,
226
- input: activator.payloadConverter.toPayload(input),
231
+ input: activator.payloadConverter.toPayload(input, context),
227
232
  scheduleToCloseTimeout: msOptionalToTs(options?.scheduleToCloseTimeout),
228
233
  scheduleToStartTimeout: msOptionalToTs(options?.scheduleToStartTimeout),
229
234
  startToCloseTimeout: msOptionalToTs(options?.startToCloseTimeout),
230
235
  cancellationType: encodeNexusOperationCancellationType(options?.cancellationType),
231
236
  },
232
- userMetadata: userMetadataToPayload(activator.payloadConverter, options?.summary, undefined),
237
+ userMetadata: userMetadataToPayload(activator.payloadConverter, options?.summary, undefined, context),
233
238
  });
234
239
 
235
240
  activator.completions.nexusOperationStart.set(seq, {
@@ -0,0 +1,21 @@
1
+ export function fillWithRandom(random: () => number, bytes: Uint8Array): Uint8Array {
2
+ for (let i = 0; i < bytes.length; ++i) {
3
+ bytes[i] = Math.floor(random() * 0x100);
4
+ }
5
+ return bytes;
6
+ }
7
+
8
+ export function uuid4FromRandom(random: () => number): string {
9
+ const ho = (n: number, p: number) => n.toString(16).padStart(p, '0');
10
+ const view = new DataView(new ArrayBuffer(16));
11
+ view.setUint32(0, (random() * 0x100000000) >>> 0);
12
+ view.setUint32(4, (random() * 0x100000000) >>> 0);
13
+ view.setUint32(8, (random() * 0x100000000) >>> 0);
14
+ view.setUint32(12, (random() * 0x100000000) >>> 0);
15
+ view.setUint8(6, (view.getUint8(6) & 0xf) | 0x40);
16
+ view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80);
17
+ return `${ho(view.getUint32(0), 8)}-${ho(view.getUint16(4), 4)}-${ho(view.getUint16(6), 4)}-${ho(
18
+ view.getUint16(8),
19
+ 4
20
+ )}-${ho(view.getUint32(10), 8)}${ho(view.getUint16(14), 4)}`;
21
+ }
@@ -0,0 +1,18 @@
1
+ import { encode } from '@temporalio/common/lib/encoding';
2
+
3
+ const RANDOM_STREAM_SEED_PREFIX = Array.from(encode('temporal-workflow-random-stream-v1'));
4
+
5
+ function encodeU32(value: number): number[] {
6
+ return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];
7
+ }
8
+
9
+ export function deriveAleaSeed(seed: number[], namespace: string): number[] {
10
+ const namespaceBytes = Array.from(encode(namespace));
11
+ return [
12
+ ...RANDOM_STREAM_SEED_PREFIX,
13
+ ...encodeU32(seed.length),
14
+ ...seed,
15
+ ...encodeU32(namespaceBytes.length),
16
+ ...namespaceBytes,
17
+ ];
18
+ }