@lifi/composer-sdk
Version:
Public Composer SDK for building and submitting flows
1 lines • 8.45 kB
Source Map (JSON)
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type {\n ComposeErrorKind,\n FailedPreparedOp,\n GenericComposeErrorKind,\n SimulationRevert,\n} from '@lifi/compose-spec';\n\nimport {\n parseServerErrorBody,\n type ServerErrorBody,\n} from './responseSchemas.js';\n\n/**\n * Machine-readable error codes returned by the SDK.\n *\n * - `NETWORK_ERROR` — The HTTP request failed (DNS, timeout, connection refused).\n * - `VALIDATION_ERROR` — The server rejected the request (HTTP 400/422).\n * - `UNAUTHENTICATED` — The request lacks valid authentication credentials (HTTP 401).\n * - `FORBIDDEN` — The server understood the request but refuses to authorise it (HTTP 403).\n * - `SERVER_ERROR` — The server returned a 5xx status.\n * - `RATE_LIMITED` — The server returned HTTP 429.\n * - `NOT_FOUND` — The requested resource does not exist (HTTP 404).\n * - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.\n */\nexport type ComposeErrorCode =\n | 'NETWORK_ERROR'\n | 'VALIDATION_ERROR'\n | 'UNAUTHENTICATED'\n | 'FORBIDDEN'\n | 'SERVER_ERROR'\n | 'RATE_LIMITED'\n | 'NOT_FOUND'\n | 'UNKNOWN_ERROR';\n\n/**\n * Error class for all failures originating from the Compose SDK or API.\n *\n * Includes structured metadata beyond the error message to support\n * programmatic error handling.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposeError(e) && e.code === 'VALIDATION_ERROR') {\n * console.error('Invalid request:', e.message, e.path);\n * }\n * }\n * ```\n */\nexport class ComposeError extends Error {\n override readonly name = 'ComposeError';\n /** Machine-readable error category. */\n readonly code: ComposeErrorCode;\n /** HTTP status code, when the error originated from an HTTP response. */\n readonly status?: number;\n /** The request URL that produced the error. */\n readonly url?: string;\n /** Server-provided error kind for finer-grained classification. */\n readonly kind?: ComposeErrorKind;\n /** JSON-pointer path to the field that caused a validation error. */\n readonly path?: string;\n /**\n * Simulation revert diagnostics attached to `simulation_revert` errors.\n * Contains the raw error bytes and decoded error candidates when the\n * backend can parse the revert reason.\n */\n readonly details?: SimulationRevert;\n /**\n * The prepared ops that failed, attached to `preparation_error` errors.\n * Each entry carries the `callId` of the failing node so callers can drop\n * the unroutable legs and resubmit a smaller flow.\n */\n readonly failedOps?: readonly FailedPreparedOp[];\n /**\n * The `callId`s of the prepared ops that succeeded, attached to\n * `preparation_error` errors alongside {@link ComposeError.failedOps}.\n */\n readonly succeededOps?: readonly string[];\n\n constructor(\n code: ComposeErrorCode,\n message: string,\n options?: {\n status?: number;\n url?: string;\n cause?: unknown;\n kind?: ComposeErrorKind;\n path?: string;\n details?: SimulationRevert;\n failedOps?: readonly FailedPreparedOp[];\n succeededOps?: readonly string[];\n },\n ) {\n super(message, { cause: options?.cause });\n this.code = code;\n this.status = options?.status;\n this.url = options?.url;\n this.kind = options?.kind;\n this.path = options?.path;\n this.details = options?.details;\n this.failedOps = options?.failedOps;\n this.succeededOps = options?.succeededOps;\n }\n}\n\n/**\n * The per-op preparation diagnostics carried by `preparation_error` responses,\n * with both arrays known to be present. Produced by narrowing with\n * {@link isComposePreparationError}.\n */\nexport interface ComposePreparationOps {\n readonly failedOps: readonly FailedPreparedOp[];\n readonly succeededOps: readonly string[];\n}\n\n/**\n * Type guard that narrows an unknown error to {@link ComposeError}.\n * @param e - The value to check.\n * @returns `true` if `e` is an instance of `ComposeError`.\n */\nexport const isComposeError = (e: unknown): e is ComposeError =>\n e instanceof ComposeError ||\n (e instanceof Error && e.name === 'ComposeError' && 'code' in e);\n\n/**\n * Type guard for `preparation_error` failures (HTTP 422): a mixed basket where\n * some prepared ops failed and others succeeded. Narrows both\n * {@link ComposeError.failedOps} and {@link ComposeError.succeededOps} to\n * present, so callers can read them without a cast or a truthiness check.\n *\n * @param e - The value to check.\n * @returns `true` if `e` is a `ComposeError` carrying per-op preparation diagnostics.\n *\n * @example\n * ```ts\n * try {\n * await builder.compile(run);\n * } catch (e) {\n * if (isComposePreparationError(e)) {\n * console.error('Failed ops:', e.failedOps);\n * console.error('Succeeded ops:', e.succeededOps);\n * }\n * }\n * ```\n */\nexport const isComposePreparationError = (\n e: unknown,\n): e is ComposeError & ComposePreparationOps =>\n isComposeError(e) &&\n e.kind === 'preparation_error' &&\n Array.isArray(e.failedOps) &&\n Array.isArray(e.succeededOps);\n\nconst STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<\n number,\n ComposeErrorCode\n>([\n [400, 'VALIDATION_ERROR'],\n [401, 'UNAUTHENTICATED'],\n [403, 'FORBIDDEN'],\n [404, 'NOT_FOUND'],\n [422, 'VALIDATION_ERROR'],\n [429, 'RATE_LIMITED'],\n]);\n\nconst tryParseErrorBody = (body: string): ServerErrorBody | null => {\n let json: unknown;\n try {\n json = JSON.parse(body);\n } catch {\n return null;\n }\n return parseServerErrorBody(json);\n};\n\n/**\n * Constructs a {@link ComposeError} from an HTTP error response, extracting\n * structured error details from the response body when available.\n *\n * @param status - The HTTP status code.\n * @param body - The raw response body text.\n * @param url - The request URL that produced the error.\n * @returns A `ComposeError` with the appropriate error code and metadata.\n */\nexport const errorFromHttpResponse = (\n status: number,\n body: string,\n url: string,\n): ComposeError => {\n const parsed = tryParseErrorBody(body);\n const serverError = parsed?.error;\n\n return new ComposeError(\n STATUS_TO_CODE.get(status) ??\n (status >= 500 ? 'SERVER_ERROR' : 'UNKNOWN_ERROR'),\n (serverError?.message ?? body) || `HTTP ${status}`,\n {\n status,\n url,\n kind: serverError?.kind as ComposeErrorKind | undefined,\n path: serverError?.path,\n details: serverError?.details,\n // The wire schema types `kind` as a bare string; the server only ever\n // reports generic kinds for individual ops.\n failedOps: serverError?.failedOps?.map((op) => ({\n ...op,\n kind: op.kind as GenericComposeErrorKind,\n })),\n succeededOps: serverError?.succeededOps,\n },\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,6BAGO;AAyCA,MAAM,qBAAqB,MAAM;AAAA,EACpB,OAAO;AAAA;AAAA,EAEhB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EAET,YACE,MACA,SACA,SAUA;AACA,UAAM,SAAS,EAAE,OAAO,SAAS,MAAM,CAAC;AACxC,SAAK,OAAO;AACZ,SAAK,SAAS,SAAS;AACvB,SAAK,MAAM,SAAS;AACpB,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,SAAS;AACrB,SAAK,UAAU,SAAS;AACxB,SAAK,YAAY,SAAS;AAC1B,SAAK,eAAe,SAAS;AAAA,EAC/B;AACF;AAiBO,MAAM,iBAAiB,CAAC,MAC7B,aAAa,gBACZ,aAAa,SAAS,EAAE,SAAS,kBAAkB,UAAU;AAuBzD,MAAM,4BAA4B,CACvC,MAEA,eAAe,CAAC,KAChB,EAAE,SAAS,uBACX,MAAM,QAAQ,EAAE,SAAS,KACzB,MAAM,QAAQ,EAAE,YAAY;AAE9B,MAAM,iBAAwD,oBAAI,IAGhE;AAAA,EACA,CAAC,KAAK,kBAAkB;AAAA,EACxB,CAAC,KAAK,iBAAiB;AAAA,EACvB,CAAC,KAAK,WAAW;AAAA,EACjB,CAAC,KAAK,WAAW;AAAA,EACjB,CAAC,KAAK,kBAAkB;AAAA,EACxB,CAAC,KAAK,cAAc;AACtB,CAAC;AAED,MAAM,oBAAoB,CAAC,SAAyC;AAClE,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAO,6CAAqB,IAAI;AAClC;AAWO,MAAM,wBAAwB,CACnC,QACA,MACA,QACiB;AACjB,QAAM,SAAS,kBAAkB,IAAI;AACrC,QAAM,cAAc,QAAQ;AAE5B,SAAO,IAAI;AAAA,IACT,eAAe,IAAI,MAAM,MACtB,UAAU,MAAM,iBAAiB;AAAA,KACnC,aAAa,WAAW,SAAS,QAAQ,MAAM;AAAA,IAChD;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM,aAAa;AAAA,MACnB,MAAM,aAAa;AAAA,MACnB,SAAS,aAAa;AAAA;AAAA;AAAA,MAGtB,WAAW,aAAa,WAAW,IAAI,CAAC,QAAQ;AAAA,QAC9C,GAAG;AAAA,QACH,MAAM,GAAG;AAAA,MACX,EAAE;AAAA,MACF,cAAc,aAAa;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}