UNPKG

@temporalio/common

Version:

Common library for code that's used across the Client, Worker, and/or Workflow

251 lines 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultFailureConverter = void 0; exports.cutoffStackTrace = cutoffStackTrace; const failure_1 = require("../failure"); const type_helpers_1 = require("../type-helpers"); const time_1 = require("../time"); const payload_converter_1 = require("./payload-converter"); function combineRegExp(...regexps) { return new RegExp(regexps.map((x) => `(?:${x.source})`).join('|')); } /** * Stack traces will be cutoff when on of these patterns is matched */ const CUTOFF_STACK_PATTERNS = combineRegExp( /** Activity execution */ /\s+at Activity\.execute \(.*[\\/]worker[\\/](?:src|lib)[\\/]activity\.[jt]s:\d+:\d+\)/, /** Workflow activation */ /\s+at Activator\.\S+NextHandler \(.*[\\/]workflow[\\/](?:src|lib)[\\/]internals\.[jt]s:\d+:\d+\)/, /** Workflow run anything in context */ /\s+at Script\.runInContext \((?:node:vm|vm\.js):\d+:\d+\)/); /** * Any stack trace frames that match any of those wil be dopped. * The "null." prefix on some cases is to avoid https://github.com/nodejs/node/issues/42417 */ const DROPPED_STACK_FRAMES_PATTERNS = combineRegExp( /** Internal functions used to recursively chain interceptors */ /\s+at (null\.)?next \(.*[\\/]common[\\/](?:src|lib)[\\/]interceptors\.[jt]s:\d+:\d+\)/, /** Internal functions used to recursively chain interceptors */ /\s+at (null\.)?executeNextHandler \(.*[\\/]worker[\\/](?:src|lib)[\\/]activity\.[jt]s:\d+:\d+\)/); /** * Cuts out the framework part of a stack trace, leaving only user code entries */ function cutoffStackTrace(stack) { const lines = (stack ?? '').split(/\r?\n/); const acc = Array(); for (const line of lines) { if (CUTOFF_STACK_PATTERNS.test(line)) break; if (!DROPPED_STACK_FRAMES_PATTERNS.test(line)) acc.push(line); } return acc.join('\n'); } /** * Default, cross-language-compatible Failure converter. * * By default, it will leave error messages and stack traces as plain text. In order to encrypt them, set * `encodeCommonAttributes` to `true` in the constructor options and use a {@link PayloadCodec} that can encrypt / * decrypt Payloads in your {@link WorkerOptions.dataConverter | Worker} and * {@link ClientOptions.dataConverter | Client options}. */ class DefaultFailureConverter { constructor(options) { const { encodeCommonAttributes } = options ?? {}; this.options = { encodeCommonAttributes: encodeCommonAttributes ?? false, }; } /** * Converts a Failure proto message to a JS Error object. * * Does not set common properties, that is done in {@link failureToError}. */ failureToErrorInner(failure, payloadConverter) { if (failure.applicationFailureInfo) { return new failure_1.ApplicationFailure(failure.message ?? undefined, failure.applicationFailureInfo.type, Boolean(failure.applicationFailureInfo.nonRetryable), (0, payload_converter_1.arrayFromPayloads)(payloadConverter, failure.applicationFailureInfo.details?.payloads), this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } if (failure.serverFailureInfo) { return new failure_1.ServerFailure(failure.message ?? undefined, Boolean(failure.serverFailureInfo.nonRetryable), this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } if (failure.timeoutFailureInfo) { return new failure_1.TimeoutFailure(failure.message ?? undefined, (0, payload_converter_1.fromPayloadsAtIndex)(payloadConverter, 0, failure.timeoutFailureInfo.lastHeartbeatDetails?.payloads), (0, failure_1.decodeTimeoutType)(failure.timeoutFailureInfo.timeoutType)); } if (failure.terminatedFailureInfo) { return new failure_1.TerminatedFailure(failure.message ?? undefined, this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } if (failure.canceledFailureInfo) { return new failure_1.CancelledFailure(failure.message ?? undefined, (0, payload_converter_1.arrayFromPayloads)(payloadConverter, failure.canceledFailureInfo.details?.payloads), this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } if (failure.resetWorkflowFailureInfo) { return new failure_1.ApplicationFailure(failure.message ?? undefined, 'ResetWorkflow', false, (0, payload_converter_1.arrayFromPayloads)(payloadConverter, failure.resetWorkflowFailureInfo.lastHeartbeatDetails?.payloads), this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } if (failure.childWorkflowExecutionFailureInfo) { const { namespace, workflowType, workflowExecution, retryState } = failure.childWorkflowExecutionFailureInfo; if (!(workflowType?.name && workflowExecution)) { throw new TypeError('Missing attributes on childWorkflowExecutionFailureInfo'); } return new failure_1.ChildWorkflowFailure(namespace ?? undefined, workflowExecution, workflowType.name, (0, failure_1.decodeRetryState)(retryState), this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } if (failure.activityFailureInfo) { if (!failure.activityFailureInfo.activityType?.name) { throw new TypeError('Missing activityType?.name on activityFailureInfo'); } return new failure_1.ActivityFailure(failure.message ?? undefined, failure.activityFailureInfo.activityType.name, failure.activityFailureInfo.activityId ?? undefined, (0, failure_1.decodeRetryState)(failure.activityFailureInfo.retryState), failure.activityFailureInfo.identity ?? undefined, this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } return new failure_1.TemporalFailure(failure.message ?? undefined, this.optionalFailureToOptionalError(failure.cause, payloadConverter)); } failureToError(failure, payloadConverter) { if (failure.encodedAttributes) { const attrs = payloadConverter.fromPayload(failure.encodedAttributes); // Don't apply encodedAttributes unless they conform to an expected schema if (typeof attrs === 'object' && attrs !== null) { const { message, stack_trace } = attrs; // Avoid mutating the argument failure = { ...failure }; if (typeof message === 'string') { failure.message = message; } if (typeof stack_trace === 'string') { failure.stackTrace = stack_trace; } } } const err = this.failureToErrorInner(failure, payloadConverter); err.stack = failure.stackTrace ?? ''; err.failure = failure; return err; } errorToFailure(err, payloadConverter) { const failure = this.errorToFailureInner(err, payloadConverter); if (this.options.encodeCommonAttributes) { const { message, stackTrace } = failure; failure.message = 'Encoded failure'; failure.stackTrace = ''; failure.encodedAttributes = payloadConverter.toPayload({ message, stack_trace: stackTrace }); } return failure; } errorToFailureInner(err, payloadConverter) { if (err instanceof failure_1.TemporalFailure) { if (err.failure) return err.failure; const base = { message: err.message, stackTrace: cutoffStackTrace(err.stack), cause: this.optionalErrorToOptionalFailure(err.cause, payloadConverter), source: failure_1.FAILURE_SOURCE, }; if (err instanceof failure_1.ActivityFailure) { return { ...base, activityFailureInfo: { ...err, retryState: (0, failure_1.encodeRetryState)(err.retryState), activityType: { name: err.activityType }, }, }; } if (err instanceof failure_1.ChildWorkflowFailure) { return { ...base, childWorkflowExecutionFailureInfo: { ...err, retryState: (0, failure_1.encodeRetryState)(err.retryState), workflowExecution: err.execution, workflowType: { name: err.workflowType }, }, }; } if (err instanceof failure_1.ApplicationFailure) { return { ...base, applicationFailureInfo: { type: err.type, nonRetryable: err.nonRetryable, details: err.details && err.details.length ? { payloads: (0, payload_converter_1.toPayloads)(payloadConverter, ...err.details) } : undefined, nextRetryDelay: (0, time_1.msOptionalToTs)(err.nextRetryDelay), }, }; } if (err instanceof failure_1.CancelledFailure) { return { ...base, canceledFailureInfo: { details: err.details && err.details.length ? { payloads: (0, payload_converter_1.toPayloads)(payloadConverter, ...err.details) } : undefined, }, }; } if (err instanceof failure_1.TimeoutFailure) { return { ...base, timeoutFailureInfo: { timeoutType: (0, failure_1.encodeTimeoutType)(err.timeoutType), lastHeartbeatDetails: err.lastHeartbeatDetails ? { payloads: (0, payload_converter_1.toPayloads)(payloadConverter, err.lastHeartbeatDetails) } : undefined, }, }; } if (err instanceof failure_1.ServerFailure) { return { ...base, serverFailureInfo: { nonRetryable: err.nonRetryable }, }; } if (err instanceof failure_1.TerminatedFailure) { return { ...base, terminatedFailureInfo: {}, }; } // Just a TemporalFailure return base; } const base = { source: failure_1.FAILURE_SOURCE, }; if ((0, type_helpers_1.isError)(err)) { return { ...base, message: String(err.message) ?? '', stackTrace: cutoffStackTrace(err.stack), cause: this.optionalErrorToOptionalFailure(err.cause, payloadConverter), }; } const recommendation = ` [A non-Error value was thrown from your code. We recommend throwing Error objects so that we can provide a stack trace]`; if (typeof err === 'string') { return { ...base, message: err + recommendation }; } if (typeof err === 'object') { let message = ''; try { message = JSON.stringify(err); } catch (_err) { message = String(err); } return { ...base, message: message + recommendation }; } return { ...base, message: String(err) + recommendation }; } /** * Converts a Failure proto message to a JS Error object if defined or returns undefined. */ optionalFailureToOptionalError(failure, payloadConverter) { return failure ? this.failureToError(failure, payloadConverter) : undefined; } /** * Converts an error to a Failure proto message if defined or returns undefined */ optionalErrorToOptionalFailure(err, payloadConverter) { return err ? this.errorToFailure(err, payloadConverter) : undefined; } } exports.DefaultFailureConverter = DefaultFailureConverter; //# sourceMappingURL=failure-converter.js.map