UNPKG

@temporalio/workflow

Version:
1,112 lines 48.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.workflowMetadataQuery = exports.enhancedStackTraceQuery = exports.stackTraceQuery = exports.NotAnActivityMethod = void 0; exports.addDefaultWorkflowOptions = addDefaultWorkflowOptions; exports.sleep = sleep; exports.scheduleActivity = scheduleActivity; exports.scheduleLocalActivity = scheduleLocalActivity; exports.proxyActivities = proxyActivities; exports.proxyLocalActivities = proxyLocalActivities; exports.getExternalWorkflowHandle = getExternalWorkflowHandle; exports.startChild = startChild; exports.executeChild = executeChild; exports.workflowInfo = workflowInfo; exports.currentUpdateInfo = currentUpdateInfo; exports.inWorkflowContext = inWorkflowContext; exports.makeContinueAsNewFunc = makeContinueAsNewFunc; exports.continueAsNew = continueAsNew; exports.uuid4 = uuid4; exports.patched = patched; exports.deprecatePatch = deprecatePatch; exports.condition = condition; exports.defineUpdate = defineUpdate; exports.defineSignal = defineSignal; exports.defineQuery = defineQuery; exports.setHandler = setHandler; exports.setDefaultSignalHandler = setDefaultSignalHandler; exports.upsertSearchAttributes = upsertSearchAttributes; exports.upsertMemo = upsertMemo; exports.allHandlersFinished = allHandlersFinished; const common_1 = require("@temporalio/common"); const versioning_intent_enum_1 = require("@temporalio/common/lib/versioning-intent-enum"); const time_1 = require("@temporalio/common/lib/time"); const interceptors_1 = require("@temporalio/common/lib/interceptors"); const cancellation_scope_1 = require("./cancellation-scope"); const update_scope_1 = require("./update-scope"); const interfaces_1 = require("./interfaces"); const errors_1 = require("./errors"); const global_attributes_1 = require("./global-attributes"); const stack_helpers_1 = require("./stack-helpers"); // Avoid a circular dependency (0, cancellation_scope_1.registerSleepImplementation)(sleep); /** * Adds default values of `workflowId` and `cancellationType` to given workflow options. */ function addDefaultWorkflowOptions(opts) { const { args, workflowId, ...rest } = opts; return { workflowId: workflowId ?? uuid4(), args: (args ?? []), cancellationType: interfaces_1.ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED, ...rest, }; } /** * Push a startTimer command into state accumulator and register completion */ function timerNextHandler(input) { const activator = (0, global_attributes_1.getActivator)(); return new Promise((resolve, reject) => { const scope = cancellation_scope_1.CancellationScope.current(); if (scope.consideredCancelled) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(reject)); return; } if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch((err) => { if (!activator.completions.timer.delete(input.seq)) { return; // Already resolved or never scheduled } activator.pushCommand({ cancelTimer: { seq: input.seq, }, }); reject(err); })); } activator.pushCommand({ startTimer: { seq: input.seq, startToFireTimeout: (0, time_1.msToTs)(input.durationMs), }, }); activator.completions.timer.set(input.seq, { resolve, reject, }); }); } /** * Asynchronous sleep. * * Schedules a timer on the Temporal service. * * @param ms sleep duration - number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}. * If given a negative number or 0, value will be set to 1. */ function sleep(ms) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.sleep(...) may only be used from a Workflow Execution'); const seq = activator.nextSeqs.timer++; const durationMs = Math.max(1, (0, time_1.msToNumber)(ms)); const execute = (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'startTimer', timerNextHandler); return execute({ durationMs, seq, }); } function validateActivityOptions(options) { if (options.scheduleToCloseTimeout === undefined && options.startToCloseTimeout === undefined) { throw new TypeError('Required either scheduleToCloseTimeout or startToCloseTimeout'); } } // Use same validation we use for normal activities const validateLocalActivityOptions = validateActivityOptions; /** * Push a scheduleActivity command into activator accumulator and register completion */ function scheduleActivityNextHandler({ options, args, headers, seq, activityType }) { const activator = (0, global_attributes_1.getActivator)(); validateActivityOptions(options); return new Promise((resolve, reject) => { const scope = cancellation_scope_1.CancellationScope.current(); if (scope.consideredCancelled) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(reject)); return; } if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(() => { if (!activator.completions.activity.has(seq)) { return; // Already resolved or never scheduled } activator.pushCommand({ requestCancelActivity: { seq, }, }); })); } activator.pushCommand({ scheduleActivity: { seq, activityId: options.activityId ?? `${seq}`, activityType, arguments: (0, common_1.toPayloads)(activator.payloadConverter, ...args), retryPolicy: options.retry ? (0, common_1.compileRetryPolicy)(options.retry) : undefined, taskQueue: options.taskQueue || activator.info.taskQueue, heartbeatTimeout: (0, time_1.msOptionalToTs)(options.heartbeatTimeout), scheduleToCloseTimeout: (0, time_1.msOptionalToTs)(options.scheduleToCloseTimeout), startToCloseTimeout: (0, time_1.msOptionalToTs)(options.startToCloseTimeout), scheduleToStartTimeout: (0, time_1.msOptionalToTs)(options.scheduleToStartTimeout), headers, cancellationType: (0, common_1.encodeActivityCancellationType)(options.cancellationType), doNotEagerlyExecute: !(options.allowEagerDispatch ?? true), versioningIntent: (0, versioning_intent_enum_1.versioningIntentToProto)(options.versioningIntent), }, }); activator.completions.activity.set(seq, { resolve, reject, }); }); } /** * Push a scheduleActivity command into state accumulator and register completion */ async function scheduleLocalActivityNextHandler({ options, args, headers, seq, activityType, attempt, originalScheduleTime, }) { const activator = (0, global_attributes_1.getActivator)(); // Eagerly fail the local activity (which will in turn fail the workflow task. // Do not fail on replay where the local activities may not be registered on the replay worker. if (!activator.info.unsafe.isReplaying && !activator.registeredActivityNames.has(activityType)) { throw new ReferenceError(`Local activity of type '${activityType}' not registered on worker`); } validateLocalActivityOptions(options); return new Promise((resolve, reject) => { const scope = cancellation_scope_1.CancellationScope.current(); if (scope.consideredCancelled) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(reject)); return; } if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(() => { if (!activator.completions.activity.has(seq)) { return; // Already resolved or never scheduled } activator.pushCommand({ requestCancelLocalActivity: { seq, }, }); })); } activator.pushCommand({ scheduleLocalActivity: { seq, attempt, originalScheduleTime, // Intentionally not exposing activityId as an option activityId: `${seq}`, activityType, arguments: (0, common_1.toPayloads)(activator.payloadConverter, ...args), retryPolicy: options.retry ? (0, common_1.compileRetryPolicy)(options.retry) : undefined, scheduleToCloseTimeout: (0, time_1.msOptionalToTs)(options.scheduleToCloseTimeout), startToCloseTimeout: (0, time_1.msOptionalToTs)(options.startToCloseTimeout), scheduleToStartTimeout: (0, time_1.msOptionalToTs)(options.scheduleToStartTimeout), localRetryThreshold: (0, time_1.msOptionalToTs)(options.localRetryThreshold), headers, cancellationType: (0, common_1.encodeActivityCancellationType)(options.cancellationType), }, }); activator.completions.activity.set(seq, { resolve, reject, }); }); } /** * Schedule an activity and run outbound interceptors * @hidden */ function scheduleActivity(activityType, args, options) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.scheduleActivity(...) may only be used from a Workflow Execution'); if (options === undefined) { throw new TypeError('Got empty activity options'); } const seq = activator.nextSeqs.activity++; const execute = (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'scheduleActivity', scheduleActivityNextHandler); return execute({ activityType, headers: {}, options, args, seq, }); } /** * Schedule an activity and run outbound interceptors * @hidden */ async function scheduleLocalActivity(activityType, args, options) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.scheduleLocalActivity(...) may only be used from a Workflow Execution'); if (options === undefined) { throw new TypeError('Got empty activity options'); } let attempt = 1; let originalScheduleTime = undefined; for (;;) { const seq = activator.nextSeqs.activity++; const execute = (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'scheduleLocalActivity', scheduleLocalActivityNextHandler); try { return (await execute({ activityType, headers: {}, options, args, seq, attempt, originalScheduleTime, })); } catch (err) { if (err instanceof errors_1.LocalActivityDoBackoff) { await sleep((0, time_1.requiredTsToMs)(err.backoff.backoffDuration, 'backoffDuration')); if (typeof err.backoff.attempt !== 'number') { throw new TypeError('Invalid backoff attempt type'); } attempt = err.backoff.attempt; originalScheduleTime = err.backoff.originalScheduleTime ?? undefined; } else { throw err; } } } } function startChildWorkflowExecutionNextHandler({ options, headers, workflowType, seq, }) { const activator = (0, global_attributes_1.getActivator)(); const workflowId = options.workflowId ?? uuid4(); const startPromise = new Promise((resolve, reject) => { const scope = cancellation_scope_1.CancellationScope.current(); if (scope.consideredCancelled) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(reject)); return; } if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(() => { const complete = !activator.completions.childWorkflowComplete.has(seq); if (!complete) { activator.pushCommand({ cancelChildWorkflowExecution: { childWorkflowSeq: seq }, }); } // Nothing to cancel otherwise })); } activator.pushCommand({ startChildWorkflowExecution: { seq, workflowId, workflowType, input: (0, common_1.toPayloads)(activator.payloadConverter, ...options.args), retryPolicy: options.retry ? (0, common_1.compileRetryPolicy)(options.retry) : undefined, taskQueue: options.taskQueue || activator.info.taskQueue, workflowExecutionTimeout: (0, time_1.msOptionalToTs)(options.workflowExecutionTimeout), workflowRunTimeout: (0, time_1.msOptionalToTs)(options.workflowRunTimeout), workflowTaskTimeout: (0, time_1.msOptionalToTs)(options.workflowTaskTimeout), namespace: activator.info.namespace, // Not configurable headers, cancellationType: (0, interfaces_1.encodeChildWorkflowCancellationType)(options.cancellationType), workflowIdReusePolicy: (0, common_1.encodeWorkflowIdReusePolicy)(options.workflowIdReusePolicy), parentClosePolicy: (0, interfaces_1.encodeParentClosePolicy)(options.parentClosePolicy), cronSchedule: options.cronSchedule, searchAttributes: options.searchAttributes ? (0, common_1.mapToPayloads)(common_1.searchAttributePayloadConverter, options.searchAttributes) : undefined, memo: options.memo && (0, common_1.mapToPayloads)(activator.payloadConverter, options.memo), versioningIntent: (0, versioning_intent_enum_1.versioningIntentToProto)(options.versioningIntent), }, }); activator.completions.childWorkflowStart.set(seq, { resolve, reject, }); }); // We construct a Promise for the completion of the child Workflow before we know // if the Workflow code will await it to capture the result in case it does. const completePromise = new Promise((resolve, reject) => { // Chain start Promise rejection to the complete Promise. (0, stack_helpers_1.untrackPromise)(startPromise.catch(reject)); activator.completions.childWorkflowComplete.set(seq, { resolve, reject, }); }); (0, stack_helpers_1.untrackPromise)(startPromise); (0, stack_helpers_1.untrackPromise)(completePromise); // Prevent unhandled rejection because the completion might not be awaited (0, stack_helpers_1.untrackPromise)(completePromise.catch(() => undefined)); const ret = new Promise((resolve) => resolve([startPromise, completePromise])); (0, stack_helpers_1.untrackPromise)(ret); return ret; } function signalWorkflowNextHandler({ seq, signalName, args, target, headers }) { const activator = (0, global_attributes_1.getActivator)(); return new Promise((resolve, reject) => { const scope = cancellation_scope_1.CancellationScope.current(); if (scope.consideredCancelled) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(reject)); return; } if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(() => { if (!activator.completions.signalWorkflow.has(seq)) { return; } activator.pushCommand({ cancelSignalWorkflow: { seq } }); })); } activator.pushCommand({ signalExternalWorkflowExecution: { seq, args: (0, common_1.toPayloads)(activator.payloadConverter, ...args), headers, signalName, ...(target.type === 'external' ? { workflowExecution: { namespace: activator.info.namespace, ...target.workflowExecution, }, } : { childWorkflowId: target.childWorkflowId, }), }, }); activator.completions.signalWorkflow.set(seq, { resolve, reject }); }); } /** * Symbol used in the return type of proxy methods to mark that an attribute on the source type is not a method. * * @see {@link ActivityInterfaceFor} * @see {@link proxyActivities} * @see {@link proxyLocalActivities} */ exports.NotAnActivityMethod = Symbol.for('__TEMPORAL_NOT_AN_ACTIVITY_METHOD'); /** * Configure Activity functions with given {@link ActivityOptions}. * * This method may be called multiple times to setup Activities with different options. * * @return a {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy | Proxy} for * which each attribute is a callable Activity function * * @example * ```ts * import { proxyActivities } from '@temporalio/workflow'; * import * as activities from '../activities'; * * // Setup Activities from module exports * const { httpGet, otherActivity } = proxyActivities<typeof activities>({ * startToCloseTimeout: '30 minutes', * }); * * // Setup Activities from an explicit interface (e.g. when defined by another SDK) * interface JavaActivities { * httpGetFromJava(url: string): Promise<string> * someOtherJavaActivity(arg1: number, arg2: string): Promise<string>; * } * * const { * httpGetFromJava, * someOtherJavaActivity * } = proxyActivities<JavaActivities>({ * taskQueue: 'java-worker-taskQueue', * startToCloseTimeout: '5m', * }); * * export function execute(): Promise<void> { * const response = await httpGet("http://example.com"); * // ... * } * ``` */ function proxyActivities(options) { if (options === undefined) { throw new TypeError('options must be defined'); } // Validate as early as possible for immediate user feedback validateActivityOptions(options); return new Proxy({}, { get(_, activityType) { if (typeof activityType !== 'string') { throw new TypeError(`Only strings are supported for Activity types, got: ${String(activityType)}`); } return function activityProxyFunction(...args) { return scheduleActivity(activityType, args, options); }; }, }); } /** * Configure Local Activity functions with given {@link LocalActivityOptions}. * * This method may be called multiple times to setup Activities with different options. * * @return a {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy | Proxy} * for which each attribute is a callable Activity function * * @see {@link proxyActivities} for examples */ function proxyLocalActivities(options) { if (options === undefined) { throw new TypeError('options must be defined'); } // Validate as early as possible for immediate user feedback validateLocalActivityOptions(options); return new Proxy({}, { get(_, activityType) { if (typeof activityType !== 'string') { throw new TypeError(`Only strings are supported for Activity types, got: ${String(activityType)}`); } return function localActivityProxyFunction(...args) { return scheduleLocalActivity(activityType, args, options); }; }, }); } // TODO: deprecate this patch after "enough" time has passed const EXTERNAL_WF_CANCEL_PATCH = '__temporal_internal_connect_external_handle_cancel_to_scope'; // The name of this patch comes from an attempt to build a generic internal patching mechanism. // That effort has been abandoned in favor of a newer WorkflowTaskCompletedMetadata based mechanism. const CONDITION_0_PATCH = '__sdk_internal_patch_number:1'; /** * Returns a client-side handle that can be used to signal and cancel an existing Workflow execution. * It takes a Workflow ID and optional run ID. */ function getExternalWorkflowHandle(workflowId, runId) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.getExternalWorkflowHandle(...) may only be used from a Workflow Execution. Consider using Client.workflow.getHandle(...) instead.)'); return { workflowId, runId, cancel() { return new Promise((resolve, reject) => { // Connect this cancel operation to the current cancellation scope. // This is behavior was introduced after v0.22.0 and is incompatible // with histories generated with previous SDK versions and thus requires // patching. // // We try to delay patching as much as possible to avoid polluting // histories unless strictly required. const scope = cancellation_scope_1.CancellationScope.current(); if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch((err) => { if (patched(EXTERNAL_WF_CANCEL_PATCH)) { reject(err); } })); } if (scope.consideredCancelled) { if (patched(EXTERNAL_WF_CANCEL_PATCH)) { return; } } const seq = activator.nextSeqs.cancelWorkflow++; activator.pushCommand({ requestCancelExternalWorkflowExecution: { seq, workflowExecution: { namespace: activator.info.namespace, workflowId, runId, }, }, }); activator.completions.cancelWorkflow.set(seq, { resolve, reject }); }); }, signal(def, ...args) { return (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'signalWorkflow', signalWorkflowNextHandler)({ seq: activator.nextSeqs.signalWorkflow++, signalName: typeof def === 'string' ? def : def.name, args, target: { type: 'external', workflowExecution: { workflowId, runId }, }, headers: {}, }); }, }; } async function startChild(workflowTypeOrFunc, options) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.startChild(...) may only be used from a Workflow Execution. Consider using Client.workflow.start(...) instead.)'); const optionsWithDefaults = addDefaultWorkflowOptions(options ?? {}); const workflowType = (0, common_1.extractWorkflowType)(workflowTypeOrFunc); const execute = (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'startChildWorkflowExecution', startChildWorkflowExecutionNextHandler); const [started, completed] = await execute({ seq: activator.nextSeqs.childWorkflow++, options: optionsWithDefaults, headers: {}, workflowType, }); const firstExecutionRunId = await started; return { workflowId: optionsWithDefaults.workflowId, firstExecutionRunId, async result() { return (await completed); }, async signal(def, ...args) { return (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'signalWorkflow', signalWorkflowNextHandler)({ seq: activator.nextSeqs.signalWorkflow++, signalName: typeof def === 'string' ? def : def.name, args, target: { type: 'child', childWorkflowId: optionsWithDefaults.workflowId, }, headers: {}, }); }, }; } async function executeChild(workflowTypeOrFunc, options) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.executeChild(...) may only be used from a Workflow Execution. Consider using Client.workflow.execute(...) instead.'); const optionsWithDefaults = addDefaultWorkflowOptions(options ?? {}); const workflowType = (0, common_1.extractWorkflowType)(workflowTypeOrFunc); const execute = (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'startChildWorkflowExecution', startChildWorkflowExecutionNextHandler); const execPromise = execute({ seq: activator.nextSeqs.childWorkflow++, options: optionsWithDefaults, headers: {}, workflowType, }); (0, stack_helpers_1.untrackPromise)(execPromise); const completedPromise = execPromise.then(([_started, completed]) => completed); (0, stack_helpers_1.untrackPromise)(completedPromise); return completedPromise; } /** * Get information about the current Workflow. * * WARNING: This function returns a frozen copy of WorkflowInfo, at the point where this method has been called. * Changes happening at later point in workflow execution will not be reflected in the returned object. * * For this reason, we recommend calling `workflowInfo()` on every access to {@link WorkflowInfo}'s fields, * rather than caching the `WorkflowInfo` object (or part of it) in a local variable. For example: * * ```ts * // GOOD * function myWorkflow() { * doSomething(workflowInfo().searchAttributes) * ... * doSomethingElse(workflowInfo().searchAttributes) * } * ``` * * vs * * ```ts * // BAD * function myWorkflow() { * const attributes = workflowInfo().searchAttributes * doSomething(attributes) * ... * doSomethingElse(attributes) * } * ``` */ function workflowInfo() { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.workflowInfo(...) may only be used from a Workflow Execution.'); return activator.info; } /** * Get information about the current update if any. * * @return Info for the current update handler the code calling this is executing * within if any. */ function currentUpdateInfo() { (0, global_attributes_1.assertInWorkflowContext)('Workflow.currentUpdateInfo(...) may only be used from a Workflow Execution.'); return update_scope_1.UpdateScope.current(); } /** * Returns whether or not code is executing in workflow context */ function inWorkflowContext() { return (0, global_attributes_1.maybeGetActivator)() !== undefined; } /** * Returns a function `f` that will cause the current Workflow to ContinueAsNew when called. * * `f` takes the same arguments as the Workflow function supplied to typeparam `F`. * * Once `f` is called, Workflow Execution immediately completes. */ function makeContinueAsNewFunc(options) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.continueAsNew(...) and Workflow.makeContinueAsNewFunc(...) may only be used from a Workflow Execution.'); const info = activator.info; const { workflowType, taskQueue, ...rest } = options ?? {}; const requiredOptions = { workflowType: workflowType ?? info.workflowType, taskQueue: taskQueue ?? info.taskQueue, ...rest, }; return (...args) => { const fn = (0, interceptors_1.composeInterceptors)(activator.interceptors.outbound, 'continueAsNew', async (input) => { const { headers, args, options } = input; throw new interfaces_1.ContinueAsNew({ workflowType: options.workflowType, arguments: (0, common_1.toPayloads)(activator.payloadConverter, ...args), headers, taskQueue: options.taskQueue, memo: options.memo && (0, common_1.mapToPayloads)(activator.payloadConverter, options.memo), searchAttributes: options.searchAttributes ? (0, common_1.mapToPayloads)(common_1.searchAttributePayloadConverter, options.searchAttributes) : undefined, workflowRunTimeout: (0, time_1.msOptionalToTs)(options.workflowRunTimeout), workflowTaskTimeout: (0, time_1.msOptionalToTs)(options.workflowTaskTimeout), versioningIntent: (0, versioning_intent_enum_1.versioningIntentToProto)(options.versioningIntent), }); }); return fn({ args, headers: {}, options: requiredOptions, }); }; } /** * {@link https://docs.temporal.io/concepts/what-is-continue-as-new/ | Continues-As-New} the current Workflow Execution * with default options. * * Shorthand for `makeContinueAsNewFunc<F>()(...args)`. (See: {@link makeContinueAsNewFunc}.) * * @example * *```ts *import { continueAsNew } from '@temporalio/workflow'; * *export async function myWorkflow(n: number): Promise<void> { * // ... Workflow logic * await continueAsNew<typeof myWorkflow>(n + 1); *} *``` */ function continueAsNew(...args) { return makeContinueAsNewFunc()(...args); } /** * Generate an RFC compliant V4 uuid. * Uses the workflow's deterministic PRNG making it safe for use within a workflow. * This function is cryptographically insecure. * See the {@link https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid | stackoverflow discussion}. */ function uuid4() { // Return the hexadecimal text representation of number `n`, padded with zeroes to be of length `p` const ho = (n, p) => n.toString(16).padStart(p, '0'); // Create a view backed by a 16-byte buffer const view = new DataView(new ArrayBuffer(16)); // Fill buffer with random values view.setUint32(0, (Math.random() * 0x100000000) >>> 0); view.setUint32(4, (Math.random() * 0x100000000) >>> 0); view.setUint32(8, (Math.random() * 0x100000000) >>> 0); view.setUint32(12, (Math.random() * 0x100000000) >>> 0); // Patch the 6th byte to reflect a version 4 UUID view.setUint8(6, (view.getUint8(6) & 0xf) | 0x40); // Patch the 8th byte to reflect a variant 1 UUID (version 4 UUIDs are) view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80); // Compile the canonical textual form from the array data return `${ho(view.getUint32(0), 8)}-${ho(view.getUint16(4), 4)}-${ho(view.getUint16(6), 4)}-${ho(view.getUint16(8), 4)}-${ho(view.getUint32(10), 8)}${ho(view.getUint16(14), 4)}`; } /** * Patch or upgrade workflow code by checking or stating that this workflow has a certain patch. * * See {@link https://docs.temporal.io/typescript/versioning | docs page} for info. * * If the workflow is replaying an existing history, then this function returns true if that * history was produced by a worker which also had a `patched` call with the same `patchId`. * If the history was produced by a worker *without* such a call, then it will return false. * * If the workflow is not currently replaying, then this call *always* returns true. * * Your workflow code should run the "new" code if this returns true, if it returns false, you * should run the "old" code. By doing this, you can maintain determinism. * * @param patchId An identifier that should be unique to this patch. It is OK to use multiple * calls with the same ID, which means all such calls will always return the same value. */ function patched(patchId) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.patch(...) and Workflow.deprecatePatch may only be used from a Workflow Execution.'); return activator.patchInternal(patchId, false); } /** * Indicate that a patch is being phased out. * * See {@link https://docs.temporal.io/typescript/versioning | docs page} for info. * * Workflows with this call may be deployed alongside workflows with a {@link patched} call, but * they must *not* be deployed while any workers still exist running old code without a * {@link patched} call, or any runs with histories produced by such workers exist. If either kind * of worker encounters a history produced by the other, their behavior is undefined. * * Once all live workflow runs have been produced by workers with this call, you can deploy workers * which are free of either kind of patch call for this ID. Workers with and without this call * may coexist, as long as they are both running the "new" code. * * @param patchId An identifier that should be unique to this patch. It is OK to use multiple * calls with the same ID, which means all such calls will always return the same value. */ function deprecatePatch(patchId) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.patch(...) and Workflow.deprecatePatch may only be used from a Workflow Execution.'); activator.patchInternal(patchId, true); } async function condition(fn, timeout) { (0, global_attributes_1.assertInWorkflowContext)('Workflow.condition(...) may only be used from a Workflow Execution.'); // Prior to 1.5.0, `condition(fn, 0)` was treated as equivalent to `condition(fn, undefined)` if (timeout === 0 && !patched(CONDITION_0_PATCH)) { return conditionInner(fn); } if (typeof timeout === 'number' || typeof timeout === 'string') { return cancellation_scope_1.CancellationScope.cancellable(async () => { try { return await Promise.race([sleep(timeout).then(() => false), conditionInner(fn).then(() => true)]); } finally { cancellation_scope_1.CancellationScope.current().cancel(); } }); } return conditionInner(fn); } function conditionInner(fn) { const activator = (0, global_attributes_1.getActivator)(); return new Promise((resolve, reject) => { const scope = cancellation_scope_1.CancellationScope.current(); if (scope.consideredCancelled) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch(reject)); return; } const seq = activator.nextSeqs.condition++; if (scope.cancellable) { (0, stack_helpers_1.untrackPromise)(scope.cancelRequested.catch((err) => { activator.blockedConditions.delete(seq); reject(err); })); } // Eager evaluation if (fn()) { resolve(); return; } activator.blockedConditions.set(seq, { fn, resolve }); }); } /** * Define an update method for a Workflow. * * A definition is used to register a handler in the Workflow via {@link setHandler} and to update a Workflow using a {@link WorkflowHandle}, {@link ChildWorkflowHandle} or {@link ExternalWorkflowHandle}. * A definition can be reused in multiple Workflows. */ function defineUpdate(name) { return { type: 'update', name, }; } /** * Define a signal method for a Workflow. * * A definition is used to register a handler in the Workflow via {@link setHandler} and to signal a Workflow using a {@link WorkflowHandle}, {@link ChildWorkflowHandle} or {@link ExternalWorkflowHandle}. * A definition can be reused in multiple Workflows. */ function defineSignal(name) { return { type: 'signal', name, }; } /** * Define a query method for a Workflow. * * A definition is used to register a handler in the Workflow via {@link setHandler} and to query a Workflow using a {@link WorkflowHandle}. * A definition can be reused in multiple Workflows. */ function defineQuery(name) { return { type: 'query', name, }; } // For Updates and Signals we want to make a public guarantee something like the // following: // // "If a WFT contains a Signal/Update, and if a handler is available for that // Signal/Update, then the handler will be executed."" // // However, that statement is not well-defined, leaving several questions open: // // 1. What does it mean for a handler to be "available"? What happens if the // handler is not present initially but is set at some point during the // Workflow code that is executed in that WFT? What happens if the handler is // set and then deleted, or replaced with a different handler? // // 2. When is the handler executed? (When it first becomes available? At the end // of the activation?) What are the execution semantics of Workflow and // Signal/Update handler code given that they are concurrent? Can the user // rely on Signal/Update side effects being reflected in the Workflow return // value, or in the value passed to Continue-As-New? If the handler is an // async function / coroutine, how much of it is executed and when is the // rest executed? // // 3. What happens if the handler is not executed? (i.e. because it wasn't // available in the sense defined by (1)) // // 4. In the case of Update, when is the validation function executed? // // The implementation for Typescript is as follows: // // 1. sdk-core sorts Signal and Update jobs (and Patches) ahead of all other // jobs. Thus if the handler is available at the start of the Activation then // the Signal/Update will be executed before Workflow code is executed. If it // is not, then the Signal/Update calls are pushed to a buffer. // // 2. On each call to setHandler for a given Signal/Update, we make a pass // through the buffer list. If a buffered job is associated with the just-set // handler, then the job is removed from the buffer and the initial // synchronous portion of the handler is invoked on that input (i.e. // preempting workflow code). // // Thus in the case of Typescript the questions above are answered as follows: // // 1. A handler is "available" if it is set at the start of the Activation or // becomes set at any point during the Activation. If the handler is not set // initially then it is executed as soon as it is set. Subsequent deletion or // replacement by a different handler has no impact because the jobs it was // handling have already been handled and are no longer in the buffer. // // 2. The handler is executed as soon as it becomes available. I.e. if the // handler is set at the start of the Activation then it is executed when // first attempting to process the Signal/Update job; alternatively, if it is // set by a setHandler call made by Workflow code, then it is executed as // part of that call (preempting Workflow code). Therefore, a user can rely // on Signal/Update side effects being reflected in e.g. the Workflow return // value, and in the value passed to Continue-As-New. Activation jobs are // processed in the order supplied by sdk-core, i.e. Signals, then Updates, // then other jobs. Within each group, the order sent by the server is // preserved. If the handler is async, it is executed up to its first yield // point. // // 3. Signal case: If a handler does not become available for a Signal job then // the job remains in the buffer. If a handler for the Signal becomes // available in a subsequent Activation (of the same or a subsequent WFT) // then the handler will be executed. If not, then the Signal will never be // responded to and this causes no error. // // Update case: If a handler does not become available for an Update job then // the Update is rejected at the end of the Activation. Thus, if a user does // not want an Update to be rejected for this reason, then it is their // responsibility to ensure that their application and workflow code interact // such that a handler is available for the Update during any Activation // which might contain their Update job. (Note that the user often has // uncertainty about which WFT their Signal/Update will appear in. For // example, if they call startWorkflow() followed by startUpdate(), then they // will typically not know whether these will be delivered in one or two // WFTs. On the other hand there are situations where they would have reason // to believe they are in the same WFT, for example if they do not start // Worker polling until after they have verified that both requests have // succeeded.) // // 4. If an Update has a validation function then it is executed immediately // prior to the handler. (Note that the validation function is required to be // synchronous). function setHandler(def, handler, options) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.setHandler(...) may only be used from a Workflow Execution.'); const description = options?.description; if (def.type === 'update') { if (typeof handler === 'function') { const updateOptions = options; const validator = updateOptions?.validator; const unfinishedPolicy = updateOptions?.unfinishedPolicy ?? common_1.HandlerUnfinishedPolicy.WARN_AND_ABANDON; activator.updateHandlers.set(def.name, { handler, validator, description, unfinishedPolicy }); activator.dispatchBufferedUpdates(); } else if (handler == null) { activator.updateHandlers.delete(def.name); } else { throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`); } } else if (def.type === 'signal') { if (typeof handler === 'function') { const signalOptions = options; const unfinishedPolicy = signalOptions?.unfinishedPolicy ?? common_1.HandlerUnfinishedPolicy.WARN_AND_ABANDON; activator.signalHandlers.set(def.name, { handler: handler, description, unfinishedPolicy }); activator.dispatchBufferedSignals(); } else if (handler == null) { activator.signalHandlers.delete(def.name); } else { throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`); } } else if (def.type === 'query') { if (typeof handler === 'function') { activator.queryHandlers.set(def.name, { handler: handler, description }); } else if (handler == null) { activator.queryHandlers.delete(def.name); } else { throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`); } } else { throw new TypeError(`Invalid definition type: ${def.type}`); } } /** * Set a signal handler function that will handle signals calls for non-registered signal names. * * Signals are dispatched to the default signal handler in the order that they were accepted by the server. * * If this function is called multiple times for a given signal or query name the last handler will overwrite any previous calls. * * @param handler a function that will handle signals for non-registered signal names, or `undefined` to unset the handler. */ function setDefaultSignalHandler(handler) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.setDefaultSignalHandler(...) may only be used from a Workflow Execution.'); if (typeof handler === 'function') { activator.defaultSignalHandler = handler; activator.dispatchBufferedSignals(); } else if (handler == null) { activator.defaultSignalHandler = undefined; } else { throw new TypeError(`Expected handler to be either a function or 'undefined'. Got: '${typeof handler}'`); } } /** * Updates this Workflow's Search Attributes by merging the provided `searchAttributes` with the existing Search * Attributes, `workflowInfo().searchAttributes`. * * For example, this Workflow code: * * ```ts * upsertSearchAttributes({ * CustomIntField: [1], * CustomBoolField: [true] * }); * upsertSearchAttributes({ * CustomIntField: [42], * CustomKeywordField: ['durable code', 'is great'] * }); * ``` * * would result in the Workflow having these Search Attributes: * * ```ts * { * CustomIntField: [42], * CustomBoolField: [true], * CustomKeywordField: ['durable code', 'is great'] * } * ``` * * @param searchAttributes The Record to merge. Use a value of `[]` to clear a Search Attribute. */ function upsertSearchAttributes(searchAttributes) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.upsertSearchAttributes(...) may only be used from a Workflow Execution.'); if (searchAttributes == null) { throw new Error('searchAttributes must be a non-null SearchAttributes'); } activator.pushCommand({ upsertWorkflowSearchAttributes: { searchAttributes: (0, common_1.mapToPayloads)(common_1.searchAttributePayloadConverter, searchAttributes), }, }); activator.mutateWorkflowInfo((info) => { return { ...info, searchAttributes: { ...info.searchAttributes, ...searchAttributes, }, }; }); } /** * Updates this Workflow's Memos by merging the provided `memo` with existing * Memos (as returned by `workflowInfo().memo`). * * New memo is merged by replacing properties of the same name _at the first * level only_. Setting a property to value `undefined` or `null` clears that * key from the Memo. * * For example: * * ```ts * upsertMemo({ * key1: value, * key3: { subkey1: value } * key4: value, * }); * upsertMemo({ * key2: value * key3: { subkey2: value } * key4: undefined, * }); * ``` * * would result in the Workflow having these Memo: * * ```ts * { * key1: value, * key2: value, * key3: { subkey2: value } // Note this object was completely replaced * // Note that key4 was completely removed * } * ``` * * @param memo The Record to merge. */ function upsertMemo(memo) { const activator = (0, global_attributes_1.assertInWorkflowContext)('Workflow.upsertMemo(...) may only be used from a Workflow Execution.'); if (memo == null) { throw new Error('memo must be a non-null Record'); } activator.pushCommand({ modifyWorkflowProperties: { upsertedMemo: { fields: (0, common_1.mapToPayloads)(activator.payloadConverter, // Convert null to undefined Object.fromEntries(Object.entries(memo).map(([k, v]) => [k, v ?? undefined]))), }, }, }); activator.mutateWorkflowInfo((info) => { return { ...info, memo: Object.fromEntries(Object.entries({ ...info.memo, ...memo, }).filter(([_, v]) => v != null)), }; }); } /** * Whether update and signal handlers have finished executing. * * Consider waiting on this condition before workflow return or continue-as-new, to prevent * interruption of in-progress handlers by workflow exit: * * ```ts * await workflow.condition(workflow.allHandlersFinished) * ``` * * @returns true if there are no in-progress update or signal handler executions. */ function allHandlersFinished() { const activator = (0, global_attributes_1.assertInWorkflowContext)('allHandlersFinished() may only be used from a Workflow Execution.'); return activator.inProgressSignals.size === 0 && activator.inProgressUpdates.size === 0; } exports.stackTraceQuery = defineQuery('__stack_trace'); exports.enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); exports.workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); //# sourceMappingURL=workflow.js.map