/** Serialize cause for JSON output */ const serializeCause = (cause: unknown): unknown => { if (cause instanceof Error) { return { name: cause.name, message: cause.message, stack: cause.stack }; } return cause; }; /** * Unrecoverable error — user code threw inside Option operations. * * @example * // Panic in generator cleanup: * Option.gen(function* () { * try { * yield* Option.none(); * } finally { * throw new Error("cleanup failed"); // Panic! * } * }); * * // Panic in combinator: * Option.some(1).map(() => { throw new Error("oops"); }); // Panic! */ export class Panic extends Error { readonly _tag = "Panic" as const; override readonly cause?: unknown; constructor(args: { message: string; cause?: unknown }) { const { message, cause } = args; super(message, cause !== undefined ? { cause } : undefined); this.cause = cause; this.name = "Panic"; Object.setPrototypeOf(this, new.target.prototype); if (cause instanceof Error && cause.stack) { const indented = cause.stack.replace(/\n/g, "\n "); this.stack = `${this.stack}\nCaused by: ${indented}`; } } toJSON(): object { return { _tag: this._tag, name: this.name, message: this.message, cause: serializeCause(this.cause), stack: this.stack, }; } } /** * Type guard for Panic instances. * * @example * if (isPanic(value)) { value.cause } */ export const isPanic = (value: unknown): value is Panic => { return value instanceof Panic; }; /** * Throw an unrecoverable Panic. * * @example * panic("something went wrong", cause); */ export const panic = (message: string, cause?: unknown): never => { throw new Panic({ message, cause }); };