1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3export class WorkflowTimeoutError extends Error {
4 name = "WorkflowTimeoutError";
5}
6
7export class WorkflowInternalError extends Error {
8 name = "WorkflowInternalError";
9}
10
11export class WorkflowFatalError extends Error {
12 name = "WorkflowFatalError";
13
14 toJSON() {
15 return {
16 name: this.name,
17 message: this.message,
18 };
19 }
20}
21
22export class NonRetryableDelayError extends WorkflowFatalError {
23 name = "NonRetryableDelayError";
24}
25
26export class PreservedNonRetryableError extends WorkflowFatalError {
27 name = "NonRetryableError";
28
29 constructor(err: Error) {
30 // When the error crosses an RPC boundary, the name gets
31 // prepended to the message (e.g. "NonRetryableError: msg",
32 // or just "NonRetryableError" if the original message was empty).
33 // Parse it back out so we surface the original message.
34 const message =
35 err.name === "NonRetryableError"
36 ? err.message
37 : err.message.replace(/^NonRetryableError:?\s*/, "");
38 super(message);
39 }
40}
41
42export class WorkflowError extends Error {
43 name = "WorkflowError";
44}
45
46export class InvalidStepReadableStreamError extends Error {
47 name = "InvalidStepReadableStreamError";
48}
49
50export class OversizedStreamChunkError extends Error {
51 name = "OversizedStreamChunkError";
52}
53
54export class UnsupportedStreamChunkError extends Error {
55 name = "UnsupportedStreamChunkError";
56}
57
58export class StreamOutputStorageLimitError extends Error {
59 name = "StreamOutputStorageLimitError";
60}
61
62export function createWorkflowError(
63 message: string,
64 errorCode: string
65): WorkflowError {
66 return new WorkflowError(`(${errorCode}) ${message}`);
67}
68
69const ABORT_PREFIX = "Aborting engine:" as const;
70
71export const ABORT_REASONS = {
72 USER_PAUSE: `${ABORT_PREFIX} User called pause`,
73 USER_RESTART: `${ABORT_PREFIX} User called restart`,
74 USER_TERMINATE: `${ABORT_PREFIX} User called terminate`,
75 USER_DELETE: `${ABORT_PREFIX} User called delete`,
76 NON_RETRYABLE_ERROR: `${ABORT_PREFIX} A step threw a NonRetryableError`,
77 NOT_SERIALISABLE: `${ABORT_PREFIX} Value is not serialisable`,
78 STORAGE_LIMIT_EXCEEDED: `${ABORT_PREFIX} Storage limit exceeded`,
79 GRACE_PERIOD_COMPLETE: `${ABORT_PREFIX} Grace period complete`,
80} as const;
81
82const ABORT_REASON_SET: ReadonlySet<string> = new Set(
83 Object.values(ABORT_REASONS)
84);
85
86function getErrorMessage(e: unknown): string | undefined {
87 if (e instanceof Error) {
88 return e.message;
89 }
90 if (typeof e === "object" && e !== null) {
91 const msg = (e as { message?: string }).message;
92 if (typeof msg === "string") {
93 return msg;
94 }
95 }
96 return undefined;
97}
98
99export function isAbortError(e: unknown): boolean {
100 const msg = getErrorMessage(e);
101 return msg !== undefined && ABORT_REASON_SET.has(msg);
102}
103
104export function isUserTriggeredPause(e: unknown): boolean {
105 return getErrorMessage(e) === ABORT_REASONS.USER_PAUSE;
106}
107
108export function isUserTriggeredRestart(e: unknown): boolean {
109 return getErrorMessage(e) === ABORT_REASONS.USER_RESTART;
110}
111
112export function isUserTriggeredTerminate(e: unknown): boolean {
113 return getErrorMessage(e) === ABORT_REASONS.USER_TERMINATE;
114}
115
116/** Checks whether an Engine RPC ended because deletion intentionally aborted it. */
117export function isUserTriggeredDelete(e: unknown): boolean {
118 return getErrorMessage(e) === ABORT_REASONS.USER_DELETE;
119}
120
121function getCompatFlag(name: string): boolean {
122 // eslint-disable-next-line @typescript-eslint/no-explicit-any -- safe globalThis access for environments where cloudflare global may not exist
123 return (globalThis as any).Cloudflare?.compatibilityFlags?.[name] ?? false;
124}
125
126export function shouldPreserveNonRetryableError(): boolean {
127 return getCompatFlag("workflows_preserve_non_retryable_error_message");
128}
129
130export function stepNotFoundError(name: string): WorkflowError {
131 return createWorkflowError(
132 `Step "${name}" not found in execution history`,
133 "instance.cannot_restart"
134 );
135}