/** * 仓库和持久化操作共享的 Result。 * * `error` 固定为 `Error`(非 `unknown`):`Result.wrap` 捕获后用 * `ExceptionHandler` 统一归一化为 `AppException`,调用方可以稳定读取 * `error.message`;需要结构化错误时可继续通过 `ExceptionHandler.handle(error)` * 得到简化后的 `{ code, message }`。 */ import { AppException, ExceptionHandler } from "@/core/errors"; import { Logger } from "./logger"; export type Result = | { readonly success: true; readonly data: T } | { readonly success: false; readonly error: Error }; /** Result.wrap 专用 logger —— 自动带 component: "Result" 标签 */ const log = new Logger("Result"); type ResultErrorLogLevel = "info" | "error"; export interface ResultWrapOptions { errorLogLevel?: | ResultErrorLogLevel | ((error: Error) => ResultErrorLogLevel); } export const Result = { /** 构造成功结果。 */ ok(data: T): Result { return { success: true, data }; }, /** * 构造失败结果。`error` 接受任意 thrown 值,内部用 `toError` 归一化为 `AppException`。 */ err(error: unknown): Result { return { success: false, error: toError(error) }; }, /** 类型守卫:true 分支。 */ isOk(r: Result): r is { readonly success: true; readonly data: T } { return r.success; }, /** 类型守卫:false 分支。 */ isErr(r: Result): r is { readonly success: false; readonly error: Error } { return !r.success; }, /** 不可变的链式 map:失败时原样传递。 */ map(r: Result, fn: (v: T) => U): Result { return r.success ? Result.ok(fn(r.data)) : r; }, /** * 捕获异步 thunk 的异常并包装成 `Result`。仓库内主要用这个把 * `*Api` 调用从「throw 风格」桥接到「Result 风格」。 * * 错误处理策略(重点): * 1. 捕获后用 `toError` 规范化为 `AppException`,原始 thrown 值放入 `cause` * 2. 用 Logger 记录(带 stack + component=Result 标签)—— 调试 dev 控制台可见, * 生产环境走 JSON pipeline 接入日志平台 * 3. 不重新抛出 —— 调用方拿到的永远是 `Result`(throw 风格调用方可在 * 最外层显式 `throw r.error`) */ async wrap( thunk: () => Promise, options?: ResultWrapOptions, ): Promise> { try { return Result.ok(await thunk()); } catch (e) { const err = toError(e); const logLevel = typeof options?.errorLogLevel === "function" ? options.errorLogLevel(err) : options?.errorLogLevel ?? "error"; const details = { err, stack: err.stack, type: err.constructor.name }; if (logLevel === "info") { log.info(details, "Result.wrap caught exception"); } else { log.error(details, "Result.wrap caught exception"); } return Result.err(err); } }, } as const; /** * 把任意 thrown 值规范化为 `AppException`: * - 已是 `AppException` → 原样返回 * - 其他错误 → 交给 `ExceptionHandler` 收敛为 `{ code, message }` * - 原始值会保存在 `cause`,方便日志和调试继续追溯 */ export function toError(value: unknown): AppException { return ExceptionHandler.toError(value); }