UNPKG

inngest

Version:

Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.

1 lines • 13.7 kB
{"version":3,"file":"errors.cjs","names":["errorConstructors","NonRetriableError","target: unknown","z","requiredFields: (keyof SerializedError)[]"],"sources":["../../src/helpers/errors.ts"],"sourcesContent":["import stringify from \"json-stringify-safe\";\nimport {\n type SerializedError as CjsSerializedError,\n deserializeError as cjsDeserializeError,\n serializeError as cjsSerializeError,\n errorConstructors,\n} from \"serialize-error-cjs\";\nimport { z } from \"zod/v3\";\nimport { NonRetriableError } from \"../components/NonRetriableError.ts\";\nimport type { ClientOptions, OutgoingOp } from \"../types.ts\";\n\nconst SERIALIZED_KEY = \"__serialized\";\nconst SERIALIZED_VALUE = true;\n\n/**\n * Add first-class support for certain errors that we control, in addition to\n * built-in errors such as `TypeError`.\n *\n * Adding these allows these non-standard errors to be correctly serialized,\n * sent to Inngest, then deserialized back into the correct error type for users\n * to react to correctly.\n *\n * Note that these errors only support `message?: string | undefined` as the\n * input; more custom errors are not supported with this current strategy.\n */\nerrorConstructors.set(\n \"NonRetriableError\",\n NonRetriableError as ErrorConstructor,\n);\n\nexport interface SerializedError extends Readonly<CjsSerializedError> {\n readonly [SERIALIZED_KEY]: typeof SERIALIZED_VALUE;\n}\n\n/**\n * Serialise an error to a serialized JSON string.\n *\n * Errors do not serialise nicely to JSON, so we use this function to convert\n * them to a serialized JSON string. Doing this is also non-trivial for some\n * errors, so we use the `serialize-error` package to do it for us.\n *\n * See {@link https://www.npmjs.com/package/serialize-error}\n *\n * This function is a small wrapper around that package to also add a `type`\n * property to the serialised error, so that we can distinguish between\n * serialised errors and other objects.\n *\n * Will not reserialise existing serialised errors.\n */\nexport const serializeError = <\n TAllowUnknown extends boolean = false,\n TOutput extends TAllowUnknown extends true\n ? unknown\n : SerializedError = TAllowUnknown extends true ? unknown : SerializedError,\n>(\n /**\n * The suspected error to serialize.\n */\n subject: unknown,\n\n /**\n * If `true` and the error is not serializable, will return the original value\n * as `unknown` instead of coercing it to a serialized error.\n */\n allowUnknown: TAllowUnknown = false as TAllowUnknown,\n): TOutput => {\n try {\n // Try to understand if this is already done.\n // Will handle stringified errors.\n const existingSerializedError = isSerializedError(subject);\n\n if (existingSerializedError) {\n return existingSerializedError as TOutput;\n }\n\n if (typeof subject === \"object\" && subject !== null) {\n // Is an object, so let's try and serialize it.\n const serializedErr = cjsSerializeError(subject as Error);\n\n // Not a proper error was caught, so give us a chance to return `unknown`.\n if (!serializedErr.name && allowUnknown) {\n return subject as TOutput;\n }\n\n // Serialization can succeed but assign no name or message, so we'll\n // map over the result here to ensure we have everything.\n // We'll just stringify the entire subject for the message, as this at\n // least provides some context for the user.\n const ret = {\n // Ensure we spread to also capture additional properties such as\n // `cause`.\n ...serializedErr,\n\n name: serializedErr.name || \"Error\",\n message:\n serializedErr.message ||\n stringify(subject) ||\n \"Unknown error; error serialization could not find a message.\",\n stack: serializedErr.stack || \"\",\n [SERIALIZED_KEY]: SERIALIZED_VALUE,\n } as const;\n\n // If we have a cause, make sure we recursively serialize them too. We are\n // lighter with causes though; attempt to recursively serialize them, but\n // stop if we find something that doesn't work and just return `unknown`.\n let target: unknown = ret;\n const maxDepth = 5;\n for (let i = 0; i < maxDepth; i++) {\n if (\n typeof target === \"object\" &&\n target !== null &&\n \"cause\" in target &&\n target.cause\n ) {\n target = target.cause = serializeError(target.cause, true);\n continue;\n }\n\n break;\n }\n\n return ret as TOutput;\n }\n\n // If it's not an object, it's hard to parse this as an Error. In this case,\n // we'll throw an error to start attempting backup strategies.\n throw new Error(\"Error is not an object; strange throw value.\");\n } catch {\n if (allowUnknown) {\n // If we are allowed to return unknown, we'll just return the original\n // value.\n return subject as TOutput;\n }\n\n try {\n // If serialization fails, fall back to a regular Error and use the\n // original object as the message for an Error. We don't know what this\n // object looks like, so we can't do anything else with it.\n return {\n ...serializeError(\n new Error(typeof subject === \"string\" ? subject : stringify(subject)),\n false,\n ),\n // Remove the stack; it's not relevant here\n stack: \"\",\n [SERIALIZED_KEY]: SERIALIZED_VALUE,\n } as TOutput;\n } catch {\n // If this failed, then stringifying the object also failed, so we'll just\n // return a completely generic error.\n // Failing to stringify the object is very unlikely.\n return {\n name: \"Could not serialize source error\",\n message: \"Serializing the source error failed.\",\n stack: \"\",\n [SERIALIZED_KEY]: SERIALIZED_VALUE,\n } as TOutput;\n }\n }\n};\n\n/**\n * Check if an object or a string is a serialised error created by\n * {@link serializeError}.\n */\nexport const isSerializedError = (\n value: unknown,\n): SerializedError | undefined => {\n try {\n if (typeof value === \"string\") {\n const parsed = z\n .object({\n [SERIALIZED_KEY]: z.literal(SERIALIZED_VALUE),\n name: z.enum([...Array.from(errorConstructors.keys())] as [\n string,\n ...string[],\n ]),\n message: z.string(),\n stack: z.string(),\n })\n .passthrough()\n .safeParse(JSON.parse(value));\n\n if (parsed.success) {\n return parsed.data as SerializedError;\n }\n }\n\n if (typeof value === \"object\" && value !== null) {\n const objIsSerializedErr =\n Object.hasOwn(value, SERIALIZED_KEY) &&\n (value as { [SERIALIZED_KEY]: unknown })[SERIALIZED_KEY] ===\n SERIALIZED_VALUE;\n\n if (objIsSerializedErr) {\n return value as SerializedError;\n }\n }\n } catch {\n // no-op; we'll return undefined if parsing failed, as it isn't a serialized\n // error\n }\n\n return;\n};\n\n/**\n * Deserialise an error created by {@link serializeError}.\n *\n * Ensures we only deserialise errors that meet a minimum level of\n * applicability, inclusive of error handling to ensure that badly serialized\n * errors are still handled.\n */\nexport const deserializeError = <\n TAllowUnknown extends boolean = false,\n TOutput extends TAllowUnknown extends true\n ? unknown\n : Error = TAllowUnknown extends true ? unknown : Error,\n>(\n subject: Partial<SerializedError>,\n allowUnknown: TAllowUnknown = false as TAllowUnknown,\n): TOutput => {\n const requiredFields: (keyof SerializedError)[] = [\"name\", \"message\"];\n\n try {\n const hasRequiredFields = requiredFields.every((field) => {\n return Object.hasOwn(subject, field);\n });\n\n if (!hasRequiredFields) {\n throw new Error();\n }\n\n const deserializedErr = cjsDeserializeError(subject as SerializedError);\n\n if (\"cause\" in deserializedErr) {\n deserializedErr.cause = deserializeError(\n deserializedErr.cause as Partial<SerializedError>,\n true,\n );\n }\n\n return deserializedErr as TOutput;\n } catch {\n if (allowUnknown) {\n // If we are allowed to return unknown, we'll just return the original\n // value.\n return subject as TOutput;\n }\n\n const err = new Error(\"Unknown error; could not reserialize\");\n\n /**\n * Remove the stack so that it's not misleadingly shown as the Inngest\n * internals.\n */\n err.stack = undefined;\n\n return err as TOutput;\n }\n};\n\nexport enum ErrCode {\n NESTING_STEPS = \"NESTING_STEPS\",\n\n /**\n * Legacy v0 execution error code for when a function has changed and no\n * longer matches its in-progress state.\n *\n * @deprecated Not for use in latest execution method.\n */\n NON_DETERMINISTIC_FUNCTION = \"NON_DETERMINISTIC_FUNCTION\",\n\n /**\n * Legacy v0 execution error code for when a function is found to be using\n * async actions after memoziation has occurred, which v0 doesn't support.\n *\n * @deprecated Not for use in latest execution method.\n */\n ASYNC_DETECTED_AFTER_MEMOIZATION = \"ASYNC_DETECTED_AFTER_MEMOIZATION\",\n\n /**\n * Legacy v0 execution error code for when a function is found to be using\n * steps after a non-step async action has occurred.\n *\n * @deprecated Not for use in latest execution method.\n */\n STEP_USED_AFTER_ASYNC = \"STEP_USED_AFTER_ASYNC\",\n\n AUTOMATIC_PARALLEL_INDEXING = \"AUTOMATIC_PARALLEL_INDEXING\",\n\n NONDETERMINISTIC_STEPS = \"NONDETERMINISTIC_STEPS\",\n}\n\n/**\n * Given an `unknown` object, retrieve the `message` property from it, or fall\n * back to the `fallback` string if it doesn't exist or is empty.\n */\nexport const getErrorMessage = (err: unknown, fallback: string): string => {\n const { message } = z\n .object({ message: z.string().min(1) })\n .catch({ message: fallback })\n .parse(err);\n\n return message;\n};\n\nexport const fixEventKeyMissingSteps = [\n \"Set the `INNGEST_EVENT_KEY` environment variable\",\n `Pass a key to the \\`new Inngest()\\` constructor using the \\`${\n \"eventKey\" satisfies keyof ClientOptions\n }\\` option`,\n];\n\n/**\n * An error that, when thrown, indicates internally that an outgoing operation\n * contains an error.\n *\n * We use this because serialized `data` sent back to Inngest may differ from\n * the error instance itself due to middleware.\n *\n * @internal\n */\nexport class OutgoingResultError extends Error {\n public readonly result: Pick<OutgoingOp, \"data\" | \"error\">;\n\n constructor(result: Pick<OutgoingOp, \"data\" | \"error\">) {\n super(\"OutgoingOpError\");\n this.result = result;\n }\n}\n\n/**\n * Create a function that will rethrow an error with a prefix added to the\n * message.\n *\n * Useful for adding context to errors that are rethrown.\n *\n * @example\n * ```ts\n * await doSomeAction().catch(rethrowError(\"Failed to do some action\"));\n * ```\n */\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nexport const rethrowError = (prefix: string): ((err: any) => never) => {\n return (err) => {\n try {\n err.message &&= `${prefix}; ${err.message}`;\n } catch (_noopErr) {\n // no-op\n } finally {\n // biome-ignore lint/correctness/noUnsafeFinally: intentional\n throw err;\n }\n };\n};\n"],"mappings":"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"}