@temporalio/workflow
Version:
Temporal.io SDK Workflow sub-package
842 lines • 37.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Activator = void 0;
const common_1 = require("@temporalio/common");
const interceptors_1 = require("@temporalio/common/lib/interceptors");
const internal_workflow_1 = require("@temporalio/common/lib/internal-workflow");
const alea_1 = require("./alea");
const cancellation_scope_1 = require("./cancellation-scope");
const update_scope_1 = require("./update-scope");
const errors_1 = require("./errors");
const interfaces_1 = require("./interfaces");
const stack_helpers_1 = require("./stack-helpers");
const pkg_1 = __importDefault(require("./pkg"));
const flags_1 = require("./flags");
const logs_1 = require("./logs");
const StartChildWorkflowExecutionFailedCause = {
WORKFLOW_ALREADY_EXISTS: 'WORKFLOW_ALREADY_EXISTS',
};
const [_encodeStartChildWorkflowExecutionFailedCause, decodeStartChildWorkflowExecutionFailedCause] = (0, internal_workflow_1.makeProtoEnumConverters)({
[StartChildWorkflowExecutionFailedCause.WORKFLOW_ALREADY_EXISTS]: 1,
UNSPECIFIED: 0,
}, 'START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_');
/**
* Keeps all of the Workflow runtime state like pending completions for activities and timers.
*
* Implements handlers for all workflow activation jobs.
*
* Note that most methods in this class are meant to be called only from within the VM.
*
* However, a few methods may be called directly from outside the VM (essentially from `vm-shared.ts`).
* These methods are specifically marked with a comment and require careful consideration, as the
* execution context may not properly reflect that of the target workflow execution (e.g.: with Reusable
* VMs, the `global` may not have been swapped to those of that workflow execution; the active microtask
* queue may be that of the thread/process, rather than the queue of that VM context; etc). Consequently,
* methods that are meant to be called from outside of the VM must not do any of the following:
*
* - Access any global variable;
* - Create Promise objects, use async/await, or otherwise schedule microtasks;
* - Call user-defined functions, including any form of interceptor.
*/
class Activator {
constructor({ info, now, showStackTraceSources, sourceMap, getTimeOfDay, randomnessSeed, registeredActivityNames, }) {
/**
* Cache for modules - referenced in reusable-vm.ts
*/
this.moduleCache = new Map();
/**
* Map of task sequence to a Completion
*/
this.completions = {
timer: new Map(),
activity: new Map(),
childWorkflowStart: new Map(),
childWorkflowComplete: new Map(),
signalWorkflow: new Map(),
cancelWorkflow: new Map(),
};
/**
* Holds buffered Update calls until a handler is registered
*/
this.bufferedUpdates = Array();
/**
* Holds buffered signal calls until a handler is registered
*/
this.bufferedSignals = Array();
/**
* Mapping of update name to handler and validator
*/
this.updateHandlers = new Map();
/**
* Mapping of signal name to handler
*/
this.signalHandlers = new Map();
/**
* Mapping of in-progress updates to handler execution information.
*/
this.inProgressUpdates = new Map();
/**
* Mapping of in-progress signals to handler execution information.
*/
this.inProgressSignals = new Map();
/**
* A sequence number providing unique identifiers for signal handler executions.
*/
this.signalHandlerExecutionSeq = 0;
this.promiseStackStore = {
promiseToStack: new Map(),
childToParent: new Map(),
};
this.rootScope = new cancellation_scope_1.RootCancellationScope();
/**
* Mapping of query name to handler
*/
this.queryHandlers = new Map([
[
'__stack_trace',
{
handler: () => {
return this.getStackTraces()
.map((s) => s.formatted)
.join('\n\n');
},
description: 'Returns a sensible stack trace.',
},
],
[
'__enhanced_stack_trace',
{
handler: () => {
const { sourceMap } = this;
const sdk = { name: 'typescript', version: pkg_1.default.version };
const stacks = this.getStackTraces().map(({ structured: locations }) => ({ locations }));
const sources = {};
if (this.showStackTraceSources) {
for (const { locations } of stacks) {
for (const { file_path } of locations) {
if (!file_path)
continue;
const content = sourceMap?.sourcesContent?.[sourceMap?.sources.indexOf(file_path)];
if (!content)
continue;
sources[file_path] = [
{
line_offset: 0,
content,
},
];
}
}
}
return { sdk, stacks, sources };
},
description: 'Returns a stack trace annotated with source information.',
},
],
[
'__temporal_workflow_metadata',
{
handler: () => {
const workflowType = this.info.workflowType;
const queryDefinitions = Array.from(this.queryHandlers.entries()).map(([name, value]) => ({
name,
description: value.description,
}));
const signalDefinitions = Array.from(this.signalHandlers.entries()).map(([name, value]) => ({
name,
description: value.description,
}));
const updateDefinitions = Array.from(this.updateHandlers.entries()).map(([name, value]) => ({
name,
description: value.description,
}));
return {
definition: {
type: workflowType,
queryDefinitions,
signalDefinitions,
updateDefinitions,
},
};
},
description: 'Returns metadata associated with this workflow.',
},
],
]);
/**
* Loaded in {@link initRuntime}
*/
this.interceptors = {
inbound: [],
outbound: [],
internals: [],
};
/**
* Buffer that stores all generated commands, reset after each activation
*/
this.commands = [];
/**
* Stores all {@link condition}s that haven't been unblocked yet
*/
this.blockedConditions = new Map();
/**
* Is this Workflow completed?
*
* A Workflow will be considered completed if it generates a command that the
* system considers as a final Workflow command (e.g.
* completeWorkflowExecution or failWorkflowExecution).
*/
this.completed = false;
/**
* Was this Workflow cancelled?
*/
this.cancelled = false;
/**
* The next (incremental) sequence to assign when generating completable commands
*/
this.nextSeqs = {
timer: 1,
activity: 1,
childWorkflow: 1,
signalWorkflow: 1,
cancelWorkflow: 1,
condition: 1,
// Used internally to keep track of active stack traces
stack: 1,
};
this.payloadConverter = common_1.defaultPayloadConverter;
this.failureConverter = common_1.defaultFailureConverter;
/**
* Patches we know the status of for this workflow, as in {@link patched}
*/
this.knownPresentPatches = new Set();
/**
* Patches we sent to core {@link patched}
*/
this.sentPatches = new Set();
this.knownFlags = new Set();
/**
* Buffered sink calls per activation
*/
this.sinkCalls = Array();
this.getTimeOfDay = getTimeOfDay;
this.info = info;
this.now = now;
this.showStackTraceSources = showStackTraceSources;
this.sourceMap = sourceMap;
this.random = (0, alea_1.alea)(randomnessSeed);
this.registeredActivityNames = registeredActivityNames;
}
/**
* May be invoked from outside the VM.
*/
mutateWorkflowInfo(fn) {
this.info = fn(this.info);
}
getStackTraces() {
const { childToParent, promiseToStack } = this.promiseStackStore;
const internalNodes = [...childToParent.values()].reduce((acc, curr) => {
for (const p of curr) {
acc.add(p);
}
return acc;
}, new Set());
const stacks = new Map();
for (const child of childToParent.keys()) {
if (!internalNodes.has(child)) {
const stack = promiseToStack.get(child);
if (!stack || !stack.formatted)
continue;
stacks.set(stack.formatted, stack);
}
}
// Not 100% sure where this comes from, just filter it out
stacks.delete(' at Promise.then (<anonymous>)');
stacks.delete(' at Promise.then (<anonymous>)\n');
return [...stacks].map(([_, stack]) => stack);
}
/**
* May be invoked from outside the VM.
*/
getAndResetSinkCalls() {
const { sinkCalls } = this;
this.sinkCalls = [];
return sinkCalls;
}
/**
* Buffer a Workflow command to be collected at the end of the current activation.
*
* Prevents commands from being added after Workflow completion.
*/
pushCommand(cmd, complete = false) {
this.commands.push(cmd);
if (complete) {
this.completed = true;
}
}
concludeActivation() {
return {
commands: this.commands.splice(0),
usedInternalFlags: [...this.knownFlags],
};
}
async startWorkflowNextHandler({ args }) {
const { workflow } = this;
if (workflow === undefined) {
throw new common_1.IllegalStateError('Workflow uninitialized');
}
return await workflow(...args);
}
startWorkflow(activation) {
const execute = (0, interceptors_1.composeInterceptors)(this.interceptors.inbound, 'execute', this.startWorkflowNextHandler.bind(this));
(0, stack_helpers_1.untrackPromise)((0, logs_1.executeWithLifecycleLogging)(() => execute({
headers: activation.headers ?? {},
args: (0, common_1.arrayFromPayloads)(this.payloadConverter, activation.arguments),
})).then(this.completeWorkflow.bind(this), this.handleWorkflowFailure.bind(this)));
}
initializeWorkflow(activation) {
const { continuedFailure, lastCompletionResult, memo, searchAttributes } = activation;
// Most things related to initialization have already been handled in the constructor
this.mutateWorkflowInfo((info) => ({
...info,
searchAttributes: (0, common_1.mapFromPayloads)(common_1.searchAttributePayloadConverter, searchAttributes?.indexedFields) ?? {},
memo: (0, common_1.mapFromPayloads)(this.payloadConverter, memo?.fields),
lastResult: (0, common_1.fromPayloadsAtIndex)(this.payloadConverter, 0, lastCompletionResult?.payloads),
lastFailure: continuedFailure != null
? this.failureConverter.failureToError(continuedFailure, this.payloadConverter)
: undefined,
}));
}
cancelWorkflow(_activation) {
this.cancelled = true;
this.rootScope.cancel();
}
fireTimer(activation) {
// Timers are a special case where their completion might not be in Workflow state,
// this is due to immediate timer cancellation that doesn't go wait for Core.
const completion = this.maybeConsumeCompletion('timer', getSeq(activation));
completion?.resolve(undefined);
}
resolveActivity(activation) {
if (!activation.result) {
throw new TypeError('Got ResolveActivity activation with no result');
}
const { resolve, reject } = this.consumeCompletion('activity', getSeq(activation));
if (activation.result.completed) {
const completed = activation.result.completed;
const result = completed.result ? this.payloadConverter.fromPayload(completed.result) : undefined;
resolve(result);
}
else if (activation.result.failed) {
const { failure } = activation.result.failed;
const err = failure ? this.failureToError(failure) : undefined;
reject(err);
}
else if (activation.result.cancelled) {
const { failure } = activation.result.cancelled;
const err = failure ? this.failureToError(failure) : undefined;
reject(err);
}
else if (activation.result.backoff) {
reject(new errors_1.LocalActivityDoBackoff(activation.result.backoff));
}
}
resolveNexusOperationStart(_activation) {
// This will never be called as we can't produce Nexus-related commands anyway
throw new Error('Nexus is not yet implemented');
}
resolveNexusOperation(_activation) {
// This will never be called as we can't produce Nexus-related commands anyway
throw new Error('Nexus is not yet implemented');
}
resolveChildWorkflowExecutionStart(activation) {
const { resolve, reject } = this.consumeCompletion('childWorkflowStart', getSeq(activation));
if (activation.succeeded) {
resolve(activation.succeeded.runId);
}
else if (activation.failed) {
if (decodeStartChildWorkflowExecutionFailedCause(activation.failed.cause) !== 'WORKFLOW_ALREADY_EXISTS') {
throw new common_1.IllegalStateError('Got unknown StartChildWorkflowExecutionFailedCause');
}
if (!(activation.seq && activation.failed.workflowId && activation.failed.workflowType)) {
throw new TypeError('Missing attributes in activation job');
}
reject(new common_1.WorkflowExecutionAlreadyStartedError('Workflow execution already started', activation.failed.workflowId, activation.failed.workflowType));
}
else if (activation.cancelled) {
if (!activation.cancelled.failure) {
throw new TypeError('Got no failure in cancelled variant');
}
reject(this.failureToError(activation.cancelled.failure));
}
else {
throw new TypeError('Got ResolveChildWorkflowExecutionStart with no status');
}
}
resolveChildWorkflowExecution(activation) {
if (!activation.result) {
throw new TypeError('Got ResolveChildWorkflowExecution activation with no result');
}
const { resolve, reject } = this.consumeCompletion('childWorkflowComplete', getSeq(activation));
if (activation.result.completed) {
const completed = activation.result.completed;
const result = completed.result ? this.payloadConverter.fromPayload(completed.result) : undefined;
resolve(result);
}
else if (activation.result.failed) {
const { failure } = activation.result.failed;
if (failure === undefined || failure === null) {
throw new TypeError('Got failed result with no failure attribute');
}
reject(this.failureToError(failure));
}
else if (activation.result.cancelled) {
const { failure } = activation.result.cancelled;
if (failure === undefined || failure === null) {
throw new TypeError('Got cancelled result with no failure attribute');
}
reject(this.failureToError(failure));
}
}
// Intentionally non-async function so this handler doesn't show up in the stack trace
queryWorkflowNextHandler({ queryName, args }) {
const fn = this.queryHandlers.get(queryName)?.handler;
if (fn === undefined) {
const knownQueryTypes = [...this.queryHandlers.keys()].join(' ');
// Fail the query
return Promise.reject(new ReferenceError(`Workflow did not register a handler for ${queryName}. Registered queries: [${knownQueryTypes}]`));
}
try {
const ret = fn(...args);
if (ret instanceof Promise) {
return Promise.reject(new errors_1.DeterminismViolationError('Query handlers should not return a Promise'));
}
return Promise.resolve(ret);
}
catch (err) {
return Promise.reject(err);
}
}
queryWorkflow(activation) {
const { queryType, queryId, headers } = activation;
if (!(queryType && queryId)) {
throw new TypeError('Missing query activation attributes');
}
const execute = (0, interceptors_1.composeInterceptors)(this.interceptors.inbound, 'handleQuery', this.queryWorkflowNextHandler.bind(this));
execute({
queryName: queryType,
args: (0, common_1.arrayFromPayloads)(this.payloadConverter, activation.arguments),
queryId,
headers: headers ?? {},
}).then((result) => this.completeQuery(queryId, result), (reason) => this.failQuery(queryId, reason));
}
doUpdate(activation) {
const { id: updateId, protocolInstanceId, name, headers, runValidator } = activation;
if (!updateId) {
throw new TypeError('Missing activation update id');
}
if (!name) {
throw new TypeError('Missing activation update name');
}
if (!protocolInstanceId) {
throw new TypeError('Missing activation update protocolInstanceId');
}
const entry = this.updateHandlers.get(name);
if (!entry) {
this.bufferedUpdates.push(activation);
return;
}
const makeInput = () => ({
updateId,
args: (0, common_1.arrayFromPayloads)(this.payloadConverter, activation.input),
name,
headers: headers ?? {},
});
// The implementation below is responsible for upholding, and constrained
// by, the following contract:
//
// 1. If no validator is present then validation interceptors will not be run.
//
// 2. During validation, any error must fail the Update; during the Update
// itself, Temporal errors fail the Update whereas other errors fail the
// activation.
//
// 3. The handler must not see any mutations of the arguments made by the
// validator.
//
// 4. Any error when decoding/deserializing input must be caught and result
// in rejection of the Update before it is accepted, even if there is no
// validator.
//
// 5. The initial synchronous portion of the (async) Update handler should
// be executed after the (sync) validator completes such that there is
// minimal opportunity for a different concurrent task to be scheduled
// between them.
//
// 6. The stack trace view provided in the Temporal UI must not be polluted
// by promises that do not derive from user code. This implies that
// async/await syntax may not be used.
//
// Note that there is a deliberately unhandled promise rejection below.
// These are caught elsewhere and fail the corresponding activation.
const doUpdateImpl = async () => {
let input;
try {
if (runValidator && entry.validator) {
const validate = (0, interceptors_1.composeInterceptors)(this.interceptors.inbound, 'validateUpdate', this.validateUpdateNextHandler.bind(this, entry.validator));
validate(makeInput());
}
input = makeInput();
}
catch (error) {
this.rejectUpdate(protocolInstanceId, error);
return;
}
this.acceptUpdate(protocolInstanceId);
const execute = (0, interceptors_1.composeInterceptors)(this.interceptors.inbound, 'handleUpdate', this.updateNextHandler.bind(this, entry.handler));
const { unfinishedPolicy } = entry;
this.inProgressUpdates.set(updateId, { name, unfinishedPolicy, id: updateId });
const res = execute(input)
.then((result) => this.completeUpdate(protocolInstanceId, result))
.catch((error) => {
if (error instanceof common_1.TemporalFailure) {
this.rejectUpdate(protocolInstanceId, error);
}
else {
throw error;
}
})
.finally(() => this.inProgressUpdates.delete(updateId));
(0, stack_helpers_1.untrackPromise)(res);
return res;
};
(0, stack_helpers_1.untrackPromise)(update_scope_1.UpdateScope.updateWithInfo(updateId, name, doUpdateImpl));
}
async updateNextHandler(handler, { args }) {
return await handler(...args);
}
validateUpdateNextHandler(validator, { args }) {
if (validator) {
validator(...args);
}
}
dispatchBufferedUpdates() {
const bufferedUpdates = this.bufferedUpdates;
while (bufferedUpdates.length) {
const foundIndex = bufferedUpdates.findIndex((update) => this.updateHandlers.has(update.name));
if (foundIndex === -1) {
// No buffered Updates have a handler yet.
break;
}
const [update] = bufferedUpdates.splice(foundIndex, 1);
this.doUpdate(update);
}
}
rejectBufferedUpdates() {
while (this.bufferedUpdates.length) {
const update = this.bufferedUpdates.shift();
if (update) {
this.rejectUpdate(
/* eslint-disable @typescript-eslint/no-non-null-assertion */
update.protocolInstanceId, common_1.ApplicationFailure.nonRetryable(`No registered handler for update: ${update.name}`));
}
}
}
async signalWorkflowNextHandler({ signalName, args }) {
const fn = this.signalHandlers.get(signalName)?.handler;
if (fn) {
return await fn(...args);
}
else if (this.defaultSignalHandler) {
return await this.defaultSignalHandler(signalName, ...args);
}
else {
throw new common_1.IllegalStateError(`No registered signal handler for signal: ${signalName}`);
}
}
signalWorkflow(activation) {
const { signalName, headers } = activation;
if (!signalName) {
throw new TypeError('Missing activation signalName');
}
if (!this.signalHandlers.has(signalName) && !this.defaultSignalHandler) {
this.bufferedSignals.push(activation);
return;
}
// If we fall through to the default signal handler then the unfinished
// policy is WARN_AND_ABANDON; users currently have no way to silence any
// ensuing warnings.
const unfinishedPolicy = this.signalHandlers.get(signalName)?.unfinishedPolicy ?? common_1.HandlerUnfinishedPolicy.WARN_AND_ABANDON;
const signalExecutionNum = this.signalHandlerExecutionSeq++;
this.inProgressSignals.set(signalExecutionNum, { name: signalName, unfinishedPolicy });
const execute = (0, interceptors_1.composeInterceptors)(this.interceptors.inbound, 'handleSignal', this.signalWorkflowNextHandler.bind(this));
execute({
args: (0, common_1.arrayFromPayloads)(this.payloadConverter, activation.input),
signalName,
headers: headers ?? {},
})
.catch(this.handleWorkflowFailure.bind(this))
.finally(() => this.inProgressSignals.delete(signalExecutionNum));
}
dispatchBufferedSignals() {
const bufferedSignals = this.bufferedSignals;
while (bufferedSignals.length) {
if (this.defaultSignalHandler) {
// We have a default signal handler, so all signals are dispatchable
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.signalWorkflow(bufferedSignals.shift());
}
else {
const foundIndex = bufferedSignals.findIndex((signal) => this.signalHandlers.has(signal.signalName));
if (foundIndex === -1)
break;
const [signal] = bufferedSignals.splice(foundIndex, 1);
this.signalWorkflow(signal);
}
}
}
resolveSignalExternalWorkflow(activation) {
const { resolve, reject } = this.consumeCompletion('signalWorkflow', getSeq(activation));
if (activation.failure) {
reject(this.failureToError(activation.failure));
}
else {
resolve(undefined);
}
}
resolveRequestCancelExternalWorkflow(activation) {
const { resolve, reject } = this.consumeCompletion('cancelWorkflow', getSeq(activation));
if (activation.failure) {
reject(this.failureToError(activation.failure));
}
else {
resolve(undefined);
}
}
warnIfUnfinishedHandlers() {
const getWarnable = (handlerExecutions) => {
return Array.from(handlerExecutions).filter((ex) => ex.unfinishedPolicy === common_1.HandlerUnfinishedPolicy.WARN_AND_ABANDON);
};
const warnableUpdates = getWarnable(this.inProgressUpdates.values());
if (warnableUpdates.length > 0) {
logs_1.log.warn(makeUnfinishedUpdateHandlerMessage(warnableUpdates));
}
const warnableSignals = getWarnable(this.inProgressSignals.values());
if (warnableSignals.length > 0) {
logs_1.log.warn(makeUnfinishedSignalHandlerMessage(warnableSignals));
}
}
updateRandomSeed(activation) {
if (!activation.randomnessSeed) {
throw new TypeError('Expected activation with randomnessSeed attribute');
}
this.random = (0, alea_1.alea)(activation.randomnessSeed.toBytes());
}
notifyHasPatch(activation) {
if (!this.info.unsafe.isReplaying)
throw new common_1.IllegalStateError('Unexpected notifyHasPatch job on non-replay activation');
if (!activation.patchId)
throw new TypeError('notifyHasPatch missing patch id');
this.knownPresentPatches.add(activation.patchId);
}
patchInternal(patchId, deprecated) {
if (this.workflow === undefined) {
throw new common_1.IllegalStateError('Patches cannot be used before Workflow starts');
}
const usePatch = !this.info.unsafe.isReplaying || this.knownPresentPatches.has(patchId);
// Avoid sending commands for patches core already knows about.
// This optimization enables development of automatic patching tools.
if (usePatch && !this.sentPatches.has(patchId)) {
this.pushCommand({
setPatchMarker: { patchId, deprecated },
});
this.sentPatches.add(patchId);
}
return usePatch;
}
/**
* Called early while handling an activation to register known flags.
* May be invoked from outside the VM.
*/
addKnownFlags(flags) {
for (const flag of flags) {
(0, flags_1.assertValidFlag)(flag);
this.knownFlags.add(flag);
}
}
/**
* Check if an SDK Flag may be considered as enabled for the current Workflow Task.
*
* SDK flags play a role similar to the `patched()` API, but are meant for internal usage by the
* SDK itself. They make it possible for the SDK to evolve its behaviors over time, while still
* maintaining compatibility with Workflow histories produced by older SDKs, without causing
* determinism violations.
*
* May be invoked from outside the VM.
*/
hasFlag(flag) {
if (this.knownFlags.has(flag.id))
return true;
// If not replaying, enable the flag if it is configured to be enabled by default. Setting a
// flag's default to false allows progressive rollout of new feature flags, with the possibility
// of reverting back to a version of the SDK where the flag is supported but disabled by default.
// It is also useful for testing purpose.
if (!this.info.unsafe.isReplaying && flag.default) {
this.knownFlags.add(flag.id);
return true;
}
// When replaying, a flag is considered enabled if it was enabled during the original execution of
// that Workflow Task; this is normally determined by the presence of the flag ID in the corresponding
// WFT Completed's `sdkMetadata.langUsedFlags`.
//
// SDK Flag Alternate Condition provides an alternative way of determining whether a flag should
// be considered as enabled for the current WFT; e.g. by looking at the version of the SDK that
// emitted a WFT. The main use case for this is to retroactively turn on some flags for WFT emitted
// by previous SDKs that contained a bug. Alt Conditions should only be used as a last resort.
//
// Note that conditions are only evaluated while replaying. Also, alternate conditions will not
// cause the flag to be persisted to the "used flags" set, which means that further Workflow Tasks
// may not reflect this flag if the condition no longer holds. This is so to avoid incorrect
// behaviors in case where a Workflow Execution has gone through a newer SDK version then again
// through an older one.
if (this.info.unsafe.isReplaying && flag.alternativeConditions) {
for (const cond of flag.alternativeConditions) {
if (cond({ info: this.info }))
return true;
}
}
return false;
}
removeFromCache() {
throw new common_1.IllegalStateError('removeFromCache activation job should not reach workflow');
}
/**
* Transforms failures into a command to be sent to the server.
* Used to handle any failure emitted by the Workflow.
*/
async handleWorkflowFailure(error) {
if (this.cancelled && (0, errors_1.isCancellation)(error)) {
this.pushCommand({ cancelWorkflowExecution: {} }, true);
}
else if (error instanceof interfaces_1.ContinueAsNew) {
this.pushCommand({ continueAsNewWorkflowExecution: error.command }, true);
}
else {
if (!(error instanceof common_1.TemporalFailure)) {
// This results in an unhandled rejection which will fail the activation
// preventing it from completing.
throw error;
}
// Fail the workflow. We do not want to issue unfinishedHandlers warnings. To achieve that, we
// mark all handlers as completed now.
this.inProgressSignals.clear();
this.inProgressUpdates.clear();
this.pushCommand({
failWorkflowExecution: {
failure: this.errorToFailure(error),
},
}, true);
}
}
completeQuery(queryId, result) {
this.pushCommand({
respondToQuery: { queryId, succeeded: { response: this.payloadConverter.toPayload(result) } },
});
}
failQuery(queryId, error) {
this.pushCommand({
respondToQuery: {
queryId,
failed: this.errorToFailure((0, common_1.ensureTemporalFailure)(error)),
},
});
}
acceptUpdate(protocolInstanceId) {
this.pushCommand({ updateResponse: { protocolInstanceId, accepted: {} } });
}
completeUpdate(protocolInstanceId, result) {
this.pushCommand({
updateResponse: { protocolInstanceId, completed: this.payloadConverter.toPayload(result) },
});
}
rejectUpdate(protocolInstanceId, error) {
this.pushCommand({
updateResponse: {
protocolInstanceId,
rejected: this.errorToFailure((0, common_1.ensureTemporalFailure)(error)),
},
});
}
/** Consume a completion if it exists in Workflow state */
maybeConsumeCompletion(type, taskSeq) {
const completion = this.completions[type].get(taskSeq);
if (completion !== undefined) {
this.completions[type].delete(taskSeq);
}
return completion;
}
/** Consume a completion if it exists in Workflow state, throws if it doesn't */
consumeCompletion(type, taskSeq) {
const completion = this.maybeConsumeCompletion(type, taskSeq);
if (completion === undefined) {
throw new common_1.IllegalStateError(`No completion for taskSeq ${taskSeq}`);
}
return completion;
}
completeWorkflow(result) {
this.pushCommand({
completeWorkflowExecution: {
result: this.payloadConverter.toPayload(result),
},
}, true);
}
errorToFailure(err) {
return this.failureConverter.errorToFailure(err, this.payloadConverter);
}
failureToError(failure) {
return this.failureConverter.failureToError(failure, this.payloadConverter);
}
}
exports.Activator = Activator;
function getSeq(activation) {
const seq = activation.seq;
if (seq === undefined || seq === null) {
throw new TypeError(`Got activation with no seq attribute`);
}
return seq;
}
function makeUnfinishedUpdateHandlerMessage(handlerExecutions) {
const message = `
[TMPRL1102] Workflow finished while an update handler was still running. This may have interrupted work that the
update handler was doing, and the client that sent the update will receive a 'workflow execution
already completed' RPCError instead of the update result. You can wait for all update and signal
handlers to complete by using \`await workflow.condition(workflow.allHandlersFinished)\`.
Alternatively, if both you and the clients sending the update are okay with interrupting running handlers
when the workflow finishes, and causing clients to receive errors, then you can disable this warning by
passing an option when setting the handler:
\`workflow.setHandler(myUpdate, myUpdateHandler, {unfinishedPolicy: HandlerUnfinishedPolicy.ABANDON});\`.`
.replace(/\n/g, ' ')
.trim();
return `${message} The following updates were unfinished (and warnings were not disabled for their handler): ${JSON.stringify(handlerExecutions.map((ex) => ({ name: ex.name, id: ex.id })))}`;
}
function makeUnfinishedSignalHandlerMessage(handlerExecutions) {
const message = `
[TMPRL1102] Workflow finished while a signal handler was still running. This may have interrupted work that the
signal handler was doing. You can wait for all update and signal handlers to complete by using
\`await workflow.condition(workflow.allHandlersFinished)\`. Alternatively, if both you and the
clients sending the update are okay with interrupting running handlers when the workflow finishes,
then you can disable this warning by passing an option when setting the handler:
\`workflow.setHandler(mySignal, mySignalHandler, {unfinishedPolicy: HandlerUnfinishedPolicy.ABANDON});\`.`
.replace(/\n/g, ' ')
.trim();
const names = new Map();
for (const ex of handlerExecutions) {
const count = names.get(ex.name) || 0;
names.set(ex.name, count + 1);
}
return `${message} The following signals were unfinished (and warnings were not disabled for their handler): ${JSON.stringify(Array.from(names.entries()).map(([name, count]) => ({ name, count })))}`;
}
//# sourceMappingURL=internals.js.map