@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/package.json +3 -3
- package/src/current-random.ts +5 -0
- package/src/flags.ts +1 -1
- package/src/global-overrides.ts +3 -2
- package/src/index.ts +4 -2
- package/src/interceptor-composition.ts +31 -0
- package/src/interfaces.ts +5 -4
- package/src/internals.ts +213 -55
- package/src/logs.ts +1 -1
- package/src/metrics.ts +1 -1
- package/src/nexus.ts +8 -3
- package/src/random-helpers.ts +21 -0
- package/src/random-stream-seed.ts +18 -0
- package/src/random-streams.ts +132 -0
- package/src/worker-interface.ts +3 -1
- package/src/workflow.ts +99 -54
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 {
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
|
|
586
|
-
|
|
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
|
-
//
|
|
771
|
-
|
|
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
|
-
//
|
|
806
|
-
|
|
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
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
//
|
|
983
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
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
|
+
}
|