@temporalio/workflow 1.0.0-rc.0 → 1.0.1

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.
@@ -0,0 +1,614 @@
1
+ import { PayloadConverter } from '@temporalio/common';
2
+ import {
3
+ arrayFromPayloads,
4
+ defaultPayloadConverter,
5
+ ensureTemporalFailure,
6
+ errorToFailure,
7
+ failureToError,
8
+ optionalFailureToOptionalError,
9
+ TemporalFailure,
10
+ } from '@temporalio/common';
11
+ import {
12
+ checkExtends,
13
+ composeInterceptors,
14
+ IllegalStateError,
15
+ Workflow,
16
+ WorkflowQueryType,
17
+ WorkflowSignalType,
18
+ } from '@temporalio/internal-workflow-common';
19
+ import type { coresdk } from '@temporalio/proto';
20
+ import { alea, RNG } from './alea';
21
+ import { ROOT_SCOPE } from './cancellation-scope';
22
+ import { DeterminismViolationError, isCancellation, WorkflowExecutionAlreadyStartedError } from './errors';
23
+ import {
24
+ QueryInput,
25
+ SignalInput,
26
+ WorkflowExecuteInput,
27
+ WorkflowInterceptors,
28
+ WorkflowInterceptorsFactory,
29
+ } from './interceptors';
30
+ import { ContinueAsNew, WorkflowInfo } from './interfaces';
31
+ import { SinkCall } from './sinks';
32
+ import { untrackPromise } from './stack-helpers';
33
+
34
+ enum StartChildWorkflowExecutionFailedCause {
35
+ START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_UNSPECIFIED = 0,
36
+ START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_EXISTS = 1,
37
+ }
38
+
39
+ checkExtends<coresdk.child_workflow.StartChildWorkflowExecutionFailedCause, StartChildWorkflowExecutionFailedCause>();
40
+
41
+ /**
42
+ * Global store to track promise stacks for stack trace query
43
+ */
44
+ export interface PromiseStackStore {
45
+ childToParent: Map<Promise<unknown>, Set<Promise<unknown>>>;
46
+ promiseToStack: Map<Promise<unknown>, string>;
47
+ }
48
+
49
+ export type ResolveFunction<T = any> = (val: T) => any;
50
+ export type RejectFunction<E = any> = (val: E) => any;
51
+
52
+ export interface Completion {
53
+ resolve: ResolveFunction;
54
+ reject: RejectFunction;
55
+ }
56
+
57
+ export interface Condition {
58
+ fn(): boolean;
59
+ resolve(): void;
60
+ }
61
+
62
+ /**
63
+ * A class that acts as a marker for this special result type
64
+ */
65
+ export class LocalActivityDoBackoff {
66
+ public readonly name = 'LocalActivityDoBackoff';
67
+ constructor(public readonly backoff: coresdk.activity_result.IDoBackoff) {}
68
+ }
69
+
70
+ export type ActivationHandlerFunction<K extends keyof coresdk.workflow_activation.IWorkflowActivationJob> = (
71
+ activation: NonNullable<coresdk.workflow_activation.IWorkflowActivationJob[K]>
72
+ ) => void;
73
+
74
+ export type ActivationHandler = {
75
+ [P in keyof coresdk.workflow_activation.IWorkflowActivationJob]: ActivationHandlerFunction<P>;
76
+ };
77
+
78
+ export class Activator implements ActivationHandler {
79
+ workflowFunctionWasCalled = false;
80
+
81
+ public async startWorkflowNextHandler({ args }: WorkflowExecuteInput): Promise<any> {
82
+ const { workflow } = state;
83
+ if (workflow === undefined) {
84
+ throw new IllegalStateError('Workflow uninitialized');
85
+ }
86
+ let promise: Promise<any>;
87
+ try {
88
+ promise = workflow(...args);
89
+ } finally {
90
+ // Guarantee this runs even if there was an exception when invoking the Workflow function
91
+ // Otherwise this Workflow will now be queryable.
92
+ this.workflowFunctionWasCalled = true;
93
+ // Empty the buffer
94
+ const buffer = state.bufferedQueries.splice(0);
95
+ for (const activation of buffer) {
96
+ this.queryWorkflow(activation);
97
+ }
98
+ }
99
+ return await promise;
100
+ }
101
+
102
+ public startWorkflow(activation: coresdk.workflow_activation.IStartWorkflow): void {
103
+ const { info } = state;
104
+ if (info === undefined) {
105
+ throw new IllegalStateError('Workflow has not been initialized');
106
+ }
107
+ const execute = composeInterceptors(
108
+ state.interceptors.inbound,
109
+ 'execute',
110
+ this.startWorkflowNextHandler.bind(this)
111
+ );
112
+ untrackPromise(
113
+ execute({
114
+ headers: activation.headers ?? {},
115
+ args: arrayFromPayloads(state.payloadConverter, activation.arguments),
116
+ }).then(completeWorkflow, handleWorkflowFailure)
117
+ );
118
+ }
119
+
120
+ public cancelWorkflow(_activation: coresdk.workflow_activation.ICancelWorkflow): void {
121
+ state.cancelled = true;
122
+ ROOT_SCOPE.cancel();
123
+ }
124
+
125
+ public fireTimer(activation: coresdk.workflow_activation.IFireTimer): void {
126
+ // Timers are a special case where their completion might not be in Workflow state,
127
+ // this is due to immediate timer cancellation that doesn't go wait for Core.
128
+ const completion = maybeConsumeCompletion('timer', getSeq(activation));
129
+ completion?.resolve(undefined);
130
+ }
131
+
132
+ public resolveActivity(activation: coresdk.workflow_activation.IResolveActivity): void {
133
+ if (!activation.result) {
134
+ throw new TypeError('Got ResolveActivity activation with no result');
135
+ }
136
+ const { resolve, reject } = consumeCompletion('activity', getSeq(activation));
137
+ if (activation.result.completed) {
138
+ const completed = activation.result.completed;
139
+ const result = completed.result ? state.payloadConverter.fromPayload(completed.result) : undefined;
140
+ resolve(result);
141
+ } else if (activation.result.failed) {
142
+ const { failure } = activation.result.failed;
143
+ const err = optionalFailureToOptionalError(failure, state.payloadConverter);
144
+ reject(err);
145
+ } else if (activation.result.cancelled) {
146
+ const { failure } = activation.result.cancelled;
147
+ const err = optionalFailureToOptionalError(failure, state.payloadConverter);
148
+ reject(err);
149
+ } else if (activation.result.backoff) {
150
+ reject(new LocalActivityDoBackoff(activation.result.backoff));
151
+ }
152
+ }
153
+
154
+ public resolveChildWorkflowExecutionStart(
155
+ activation: coresdk.workflow_activation.IResolveChildWorkflowExecutionStart
156
+ ): void {
157
+ const { resolve, reject } = consumeCompletion('childWorkflowStart', getSeq(activation));
158
+ if (activation.succeeded) {
159
+ resolve(activation.succeeded.runId);
160
+ } else if (activation.failed) {
161
+ if (
162
+ activation.failed.cause !==
163
+ StartChildWorkflowExecutionFailedCause.START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_EXISTS
164
+ ) {
165
+ throw new IllegalStateError('Got unknown StartChildWorkflowExecutionFailedCause');
166
+ }
167
+ if (!(activation.seq && activation.failed.workflowId && activation.failed.workflowType)) {
168
+ throw new TypeError('Missing attributes in activation job');
169
+ }
170
+ reject(
171
+ new WorkflowExecutionAlreadyStartedError(
172
+ 'Workflow execution already started',
173
+ activation.failed.workflowId,
174
+ activation.failed.workflowType
175
+ )
176
+ );
177
+ } else if (activation.cancelled) {
178
+ if (!activation.cancelled.failure) {
179
+ throw new TypeError('Got no failure in cancelled variant');
180
+ }
181
+ reject(failureToError(activation.cancelled.failure, state.payloadConverter));
182
+ } else {
183
+ throw new TypeError('Got ResolveChildWorkflowExecutionStart with no status');
184
+ }
185
+ }
186
+
187
+ public resolveChildWorkflowExecution(activation: coresdk.workflow_activation.IResolveChildWorkflowExecution): void {
188
+ if (!activation.result) {
189
+ throw new TypeError('Got ResolveChildWorkflowExecution activation with no result');
190
+ }
191
+ const { resolve, reject } = consumeCompletion('childWorkflowComplete', getSeq(activation));
192
+ if (activation.result.completed) {
193
+ const completed = activation.result.completed;
194
+ const result = completed.result ? state.payloadConverter.fromPayload(completed.result) : undefined;
195
+ resolve(result);
196
+ } else if (activation.result.failed) {
197
+ const { failure } = activation.result.failed;
198
+ if (failure === undefined || failure === null) {
199
+ throw new TypeError('Got failed result with no failure attribute');
200
+ }
201
+ reject(failureToError(failure, state.payloadConverter));
202
+ } else if (activation.result.cancelled) {
203
+ const { failure } = activation.result.cancelled;
204
+ if (failure === undefined || failure === null) {
205
+ throw new TypeError('Got cancelled result with no failure attribute');
206
+ }
207
+ reject(failureToError(failure, state.payloadConverter));
208
+ }
209
+ }
210
+
211
+ // Intentionally not made function async so this handler doesn't show up in the stack trace
212
+ protected queryWorkflowNextHandler({ queryName, args }: QueryInput): Promise<unknown> {
213
+ const fn = state.queryHandlers.get(queryName);
214
+ if (fn === undefined) {
215
+ const knownQueryTypes = [...state.queryHandlers.keys()].join(' ');
216
+ // Fail the query
217
+ throw new ReferenceError(
218
+ `Workflow did not register a handler for ${queryName}. Registered queries: [${knownQueryTypes}]`
219
+ );
220
+ }
221
+ try {
222
+ const ret = fn(...args);
223
+ if (ret instanceof Promise) {
224
+ return Promise.reject(new DeterminismViolationError('Query handlers should not return a Promise'));
225
+ }
226
+ return Promise.resolve(ret);
227
+ } catch (err) {
228
+ return Promise.reject(err);
229
+ }
230
+ }
231
+
232
+ public queryWorkflow(activation: coresdk.workflow_activation.IQueryWorkflow): void {
233
+ if (!this.workflowFunctionWasCalled) {
234
+ state.bufferedQueries.push(activation);
235
+ return;
236
+ }
237
+
238
+ const { queryType, queryId, headers } = activation;
239
+ if (!(queryType && queryId)) {
240
+ throw new TypeError('Missing query activation attributes');
241
+ }
242
+
243
+ const execute = composeInterceptors(
244
+ state.interceptors.inbound,
245
+ 'handleQuery',
246
+ this.queryWorkflowNextHandler.bind(this)
247
+ );
248
+ execute({
249
+ queryName: queryType,
250
+ args: arrayFromPayloads(state.payloadConverter, activation.arguments),
251
+ queryId,
252
+ headers: headers ?? {},
253
+ }).then(
254
+ (result) => completeQuery(queryId, result),
255
+ (reason) => failQuery(queryId, reason)
256
+ );
257
+ }
258
+
259
+ public async signalWorkflowNextHandler({ signalName, args }: SignalInput): Promise<void> {
260
+ const fn = state.signalHandlers.get(signalName);
261
+ if (fn === undefined) {
262
+ throw new IllegalStateError(`No registered signal handler for signal ${signalName}`);
263
+ }
264
+ return await fn(...args);
265
+ }
266
+
267
+ public signalWorkflow(activation: coresdk.workflow_activation.ISignalWorkflow): void {
268
+ const { signalName, headers } = activation;
269
+ if (!signalName) {
270
+ throw new TypeError('Missing activation signalName');
271
+ }
272
+
273
+ const fn = state.signalHandlers.get(signalName);
274
+ if (fn === undefined) {
275
+ let buffer = state.bufferedSignals.get(signalName);
276
+ if (buffer === undefined) {
277
+ buffer = [];
278
+ state.bufferedSignals.set(signalName, buffer);
279
+ }
280
+ buffer.push(activation);
281
+ return;
282
+ }
283
+
284
+ const execute = composeInterceptors(
285
+ state.interceptors.inbound,
286
+ 'handleSignal',
287
+ this.signalWorkflowNextHandler.bind(this)
288
+ );
289
+ execute({
290
+ args: arrayFromPayloads(state.payloadConverter, activation.input),
291
+ signalName,
292
+ headers: headers ?? {},
293
+ }).catch(handleWorkflowFailure);
294
+ }
295
+
296
+ public resolveSignalExternalWorkflow(activation: coresdk.workflow_activation.IResolveSignalExternalWorkflow): void {
297
+ const { resolve, reject } = consumeCompletion('signalWorkflow', getSeq(activation));
298
+ if (activation.failure) {
299
+ reject(failureToError(activation.failure, state.payloadConverter));
300
+ } else {
301
+ resolve(undefined);
302
+ }
303
+ }
304
+
305
+ public resolveRequestCancelExternalWorkflow(
306
+ activation: coresdk.workflow_activation.IResolveRequestCancelExternalWorkflow
307
+ ): void {
308
+ const { resolve, reject } = consumeCompletion('cancelWorkflow', getSeq(activation));
309
+ if (activation.failure) {
310
+ reject(failureToError(activation.failure, state.payloadConverter));
311
+ } else {
312
+ resolve(undefined);
313
+ }
314
+ }
315
+
316
+ public updateRandomSeed(activation: coresdk.workflow_activation.IUpdateRandomSeed): void {
317
+ if (!activation.randomnessSeed) {
318
+ throw new TypeError('Expected activation with randomnessSeed attribute');
319
+ }
320
+ state.random = alea(activation.randomnessSeed.toBytes());
321
+ }
322
+
323
+ public notifyHasPatch(activation: coresdk.workflow_activation.INotifyHasPatch): void {
324
+ if (!activation.patchId) {
325
+ throw new TypeError('Notify has patch missing patch name');
326
+ }
327
+ state.knownPresentPatches.add(activation.patchId);
328
+ }
329
+
330
+ public removeFromCache(): void {
331
+ throw new IllegalStateError('removeFromCache activation job should not reach workflow');
332
+ }
333
+ }
334
+
335
+ export type WorkflowsImportFunc = () => Promise<Record<string, any>>;
336
+ export type InterceptorsImportFunc = () => Promise<Array<{ interceptors: WorkflowInterceptorsFactory }>>;
337
+
338
+ /**
339
+ * Keeps all of the Workflow runtime state like pending completions for activities and timers and the scope stack.
340
+ *
341
+ * State mutates each time the Workflow is activated.
342
+ */
343
+ export class State {
344
+ /**
345
+ * Activator executes activation jobs
346
+ */
347
+ public readonly activator = new Activator();
348
+
349
+ /**
350
+ * Map of task sequence to a Completion
351
+ */
352
+ public readonly completions = {
353
+ timer: new Map<number, Completion>(),
354
+ activity: new Map<number, Completion>(),
355
+ childWorkflowStart: new Map<number, Completion>(),
356
+ childWorkflowComplete: new Map<number, Completion>(),
357
+ signalWorkflow: new Map<number, Completion>(),
358
+ cancelWorkflow: new Map<number, Completion>(),
359
+ };
360
+
361
+ /**
362
+ * Holds buffered signal calls until a handler is registered
363
+ */
364
+ public readonly bufferedSignals = new Map<string, coresdk.workflow_activation.ISignalWorkflow[]>();
365
+
366
+ /**
367
+ * Holds buffered query calls until a handler is registered.
368
+ *
369
+ * **IMPORTANT** queries are only buffered until workflow is started.
370
+ * This is required because async interceptors might block workflow function invocation
371
+ * which delays query handler registration.
372
+ */
373
+ public readonly bufferedQueries = Array<coresdk.workflow_activation.IQueryWorkflow>();
374
+
375
+ /**
376
+ * Mapping of signal name to handler
377
+ */
378
+ public readonly signalHandlers = new Map<string, WorkflowSignalType>();
379
+
380
+ /**
381
+ * Mapping of query name to handler
382
+ */
383
+ public readonly queryHandlers = new Map<string, WorkflowQueryType>([
384
+ [
385
+ '__stack_trace',
386
+ () => {
387
+ const { childToParent, promiseToStack } = (globalThis as any).__TEMPORAL__
388
+ .promiseStackStore as PromiseStackStore;
389
+ const internalNodes = new Set(
390
+ [...childToParent.values()].reduce((acc, curr) => {
391
+ for (const p of curr) {
392
+ acc.add(p);
393
+ }
394
+ return acc;
395
+ }, new Set())
396
+ );
397
+ const stacks = new Set<string>();
398
+ for (const child of childToParent.keys()) {
399
+ if (!internalNodes.has(child)) {
400
+ const stack = promiseToStack.get(child);
401
+ if (!stack) continue;
402
+ stacks.add(stack);
403
+ }
404
+ }
405
+ // Not 100% sure where this comes from, just filter it out
406
+ stacks.delete(' at Promise.then (<anonymous>)');
407
+ stacks.delete(' at Promise.then (<anonymous>)\n');
408
+ return [...stacks].join('\n\n');
409
+ },
410
+ ],
411
+ ]);
412
+
413
+ /**
414
+ * Loaded in {@link initRuntime}
415
+ */
416
+ public interceptors: Required<WorkflowInterceptors> = { inbound: [], outbound: [], internals: [] };
417
+
418
+ /**
419
+ * Buffer that stores all generated commands, reset after each activation
420
+ */
421
+ public commands: coresdk.workflow_commands.IWorkflowCommand[] = [];
422
+
423
+ /**
424
+ * Stores all {@link condition}s that haven't been unblocked yet
425
+ */
426
+ public blockedConditions = new Map<number, Condition>();
427
+
428
+ /**
429
+ * Is this Workflow completed?
430
+ *
431
+ * A Workflow will be considered completed if it generates a command that the
432
+ * system considers as a final Workflow command (e.g.
433
+ * completeWorkflowExecution or failWorkflowExecution).
434
+ */
435
+ public completed = false;
436
+
437
+ /**
438
+ * Was this Workflow cancelled?
439
+ */
440
+ public cancelled = false;
441
+
442
+ /**
443
+ * The next (incremental) sequence to assign when generating completable commands
444
+ */
445
+ public nextSeqs = {
446
+ timer: 1,
447
+ activity: 1,
448
+ childWorkflow: 1,
449
+ signalWorkflow: 1,
450
+ cancelWorkflow: 1,
451
+ condition: 1,
452
+ // Used internally to keep track of active stack traces
453
+ stack: 1,
454
+ };
455
+
456
+ /**
457
+ * This is set every time the workflow executes an activation
458
+ */
459
+ #now: number | undefined;
460
+
461
+ get now(): number {
462
+ if (this.#now === undefined) {
463
+ throw new IllegalStateError('Tried to get Date before Workflow has been initialized');
464
+ }
465
+ return this.#now;
466
+ }
467
+
468
+ set now(value: number) {
469
+ this.#now = value;
470
+ }
471
+
472
+ /**
473
+ * Reference to the current Workflow, initialized when a Workflow is started
474
+ */
475
+ public workflow?: Workflow;
476
+
477
+ /**
478
+ * Information about the current Workflow
479
+ */
480
+ public info?: WorkflowInfo;
481
+
482
+ /**
483
+ * A deterministic RNG, used by the isolate's overridden Math.random
484
+ */
485
+ public random: RNG = function () {
486
+ throw new IllegalStateError('Tried to use Math.random before Workflow has been initialized');
487
+ };
488
+
489
+ /**
490
+ * Used to import the user workflows
491
+ *
492
+ * Injected on isolate context startup
493
+ */
494
+ public importWorkflows?: WorkflowsImportFunc;
495
+
496
+ /**
497
+ * Used to import the user interceptors
498
+ *
499
+ * Injected on isolate context startup
500
+ */
501
+ public importInterceptors?: InterceptorsImportFunc;
502
+
503
+ public payloadConverter: PayloadConverter = defaultPayloadConverter;
504
+
505
+ /**
506
+ * Patches we know the status of for this workflow, as in {@link patched}
507
+ */
508
+ public readonly knownPresentPatches = new Set<string>();
509
+
510
+ /**
511
+ * Patches we sent to core {@link patched}
512
+ */
513
+ public readonly sentPatches = new Set<string>();
514
+
515
+ sinkCalls = Array<SinkCall>();
516
+
517
+ getAndResetSinkCalls(): SinkCall[] {
518
+ const { sinkCalls } = this;
519
+ this.sinkCalls = [];
520
+ return sinkCalls;
521
+ }
522
+
523
+ /**
524
+ * Buffer a Workflow command to be collected at the end of the current activation.
525
+ *
526
+ * Prevents commands from being added after Workflow completion.
527
+ */
528
+ pushCommand(cmd: coresdk.workflow_commands.IWorkflowCommand, complete = false): void {
529
+ // Only query responses may be sent after completion
530
+ if (this.completed && !cmd.respondToQuery) return;
531
+ this.commands.push(cmd);
532
+ if (complete) {
533
+ this.completed = true;
534
+ }
535
+ }
536
+ }
537
+
538
+ export const state = new State();
539
+
540
+ function completeWorkflow(result: any) {
541
+ state.pushCommand(
542
+ {
543
+ completeWorkflowExecution: {
544
+ result: state.payloadConverter.toPayload(result),
545
+ },
546
+ },
547
+ true
548
+ );
549
+ }
550
+
551
+ /**
552
+ * Transforms failures into a command to be sent to the server.
553
+ * Used to handle any failure emitted by the Workflow.
554
+ */
555
+ export async function handleWorkflowFailure(error: unknown): Promise<void> {
556
+ if (state.cancelled && isCancellation(error)) {
557
+ state.pushCommand({ cancelWorkflowExecution: {} }, true);
558
+ } else if (error instanceof ContinueAsNew) {
559
+ state.pushCommand({ continueAsNewWorkflowExecution: error.command }, true);
560
+ } else {
561
+ if (!(error instanceof TemporalFailure)) {
562
+ // This results in an unhandled rejection which will fail the activation
563
+ // preventing it from completing.
564
+ throw error;
565
+ }
566
+
567
+ state.pushCommand(
568
+ {
569
+ failWorkflowExecution: {
570
+ failure: errorToFailure(error, state.payloadConverter),
571
+ },
572
+ },
573
+ true
574
+ );
575
+ }
576
+ }
577
+
578
+ function completeQuery(queryId: string, result: unknown) {
579
+ state.pushCommand({
580
+ respondToQuery: { queryId, succeeded: { response: state.payloadConverter.toPayload(result) } },
581
+ });
582
+ }
583
+
584
+ async function failQuery(queryId: string, error: any) {
585
+ state.pushCommand({
586
+ respondToQuery: { queryId, failed: errorToFailure(ensureTemporalFailure(error), state.payloadConverter) },
587
+ });
588
+ }
589
+
590
+ /** Consume a completion if it exists in Workflow state */
591
+ export function maybeConsumeCompletion(type: keyof State['completions'], taskSeq: number): Completion | undefined {
592
+ const completion = state.completions[type].get(taskSeq);
593
+ if (completion !== undefined) {
594
+ state.completions[type].delete(taskSeq);
595
+ }
596
+ return completion;
597
+ }
598
+
599
+ /** Consume a completion if it exists in Workflow state, throws if it doesn't */
600
+ export function consumeCompletion(type: keyof State['completions'], taskSeq: number): Completion {
601
+ const completion = maybeConsumeCompletion(type, taskSeq);
602
+ if (completion === undefined) {
603
+ throw new IllegalStateError(`No completion for taskSeq ${taskSeq}`);
604
+ }
605
+ return completion;
606
+ }
607
+
608
+ function getSeq<T extends { seq?: number | null }>(activation: T): number {
609
+ const seq = activation.seq;
610
+ if (seq === undefined || seq === null) {
611
+ throw new TypeError(`Got activation with no seq attribute`);
612
+ }
613
+ return seq;
614
+ }
package/src/sinks.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Type definitions for the Workflow end of the sinks mechanism.
3
+ *
4
+ * Sinks are a mechanism for exporting data from the Workflow isolate to the
5
+ * Node.js environment, they are necessary because the Workflow has no way to
6
+ * communicate with the outside World.
7
+ *
8
+ * Sinks are typically used for exporting logs, metrics and traces out from the
9
+ * Workflow.
10
+ *
11
+ * Sink functions may not return values to the Workflow in order to prevent
12
+ * breaking determinism.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ /**
18
+ * Any function signature can be used for Sink functions as long as the return type is `void`.
19
+ *
20
+ * When calling a Sink function, arguments are copied from the Workflow isolate to the Node.js environment using
21
+ * {@link https://nodejs.org/api/worker_threads.html#worker_threads_port_postmessage_value_transferlist | postMessage}.
22
+
23
+ * This constrains the argument types to primitives (excluding Symbols).
24
+ */
25
+ export type SinkFunction = (...args: any[]) => void;
26
+
27
+ /** A mapping of name to function, defines a single sink (e.g. logger) */
28
+ export type Sink = Record<string, SinkFunction>;
29
+ /**
30
+ * Workflow Sink are a mapping of name to {@link Sink}
31
+ */
32
+ export type Sinks = Record<string, Sink>;
33
+
34
+ /**
35
+ * Call information for a Sink
36
+ */
37
+ export interface SinkCall {
38
+ ifaceName: string;
39
+ fnName: string;
40
+ args: any[];
41
+ }
@@ -0,0 +1,11 @@
1
+ import type { PromiseStackStore } from './internals';
2
+
3
+ /**
4
+ * Helper function to remove a promise from being tracked for stack trace query purposes
5
+ */
6
+ export function untrackPromise(promise: Promise<unknown>): void {
7
+ const store = (globalThis as any).__TEMPORAL__?.promiseStackStore as PromiseStackStore | undefined;
8
+ if (!store) return;
9
+ store.childToParent.delete(promise);
10
+ store.promiseToStack.delete(promise);
11
+ }