{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n  ComposeCompilePartialData,\n  ComposeCompileRequest,\n  ComposeCompileResult,\n  ComposeCompileSuccessData,\n  ComposeManifest,\n  SimulateRequest,\n  SimulateResult,\n} from '@lifi/compose-spec';\n\nimport type { GetZapPacksOptions, ZapPackOverview } from './discovery.js';\nimport { ComposeError, errorFromHttpResponse } from './errors.js';\nimport {\n  parseCompilePartialEnvelope,\n  parseSimulateResult,\n} from './responseSchemas.js';\n\n// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)\n// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json\n// at build/test time and replace this identifier with the literal string value.\n// It is sent as the `x-lifi-composer-sdk` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsup substitution (e.g. the example harness).\ndeclare const __SDK_VERSION__: string;\n\nconst SDK_VERSION: string =\n  typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : 'dev';\n\n/**\n * Configuration for creating a low-level Compose API client.\n */\nexport interface ComposeClientOptions {\n  /** Base URL of the Compose API. */\n  readonly baseUrl: string;\n  /** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */\n  readonly fetch?: typeof globalThis.fetch;\n  /** Optional LI.FI API key. When set, sent as the `x-lifi-api-key` header on every request. */\n  readonly apiKey?: string;\n}\n\n/**\n * Low-level HTTP client for the Compose API.\n *\n * Handles request serialization, SDK version headers, and error mapping.\n * Prefer using {@link ComposeSdk} for the full builder experience. Use this\n * directly when you need to decouple request building from submission — e.g.\n * build via `sdk.request()` then submit via `client.compile()` with custom\n * retry logic or request inspection.\n */\nexport interface ComposeClient {\n  /**\n   * Fetches the server's operation manifest describing all supported operations,\n   * guards, materialisers, and preconditions.\n   * @returns The manifest document.\n   * @throws {@link ComposeError} on network, validation, or server errors.\n   */\n  readonly getManifest: () => Promise<ComposeManifest>;\n  /**\n   * Submits a compile request and returns the result.\n   *\n   * When the caller passes `simulationPolicy: 'allow-revert'` and the transaction\n   * reverts in simulation, the server responds with HTTP 206 and the SDK returns a\n   * partial result (`status: 'partial'`) instead of throwing. The partial result\n   * includes the transaction (without `gasLimit`) and revert diagnostics.\n   *\n   * @param request - The full compile request including flow and run inputs.\n   * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n   * @throws {@link ComposeError} on network, validation, or server errors.\n   */\n  readonly compile: (\n    request: ComposeCompileRequest,\n  ) => Promise<ComposeCompileResult>;\n  /**\n   * Fetches the available routing edges grouped by protocol.\n   *\n   * The edge catalog is dynamic — it reflects the current state of the\n   * backend's routing snapshot (protocols, chains, token blacklists).\n   * Results are not cached by the SDK; callers should cache as appropriate.\n   *\n   * @param options - Optional filter to restrict results to specific protocols.\n   * @returns An array of {@link ZapPackOverview} objects, one per protocol.\n   * @throws {@link ComposeError} on network or server errors (503 when the\n   *   routing catalog is not yet initialized).\n   */\n  readonly getZapPacks: (\n    options?: GetZapPacksOptions,\n  ) => Promise<readonly ZapPackOverview[]>;\n  /**\n   * Simulates a raw, pre-encoded transaction against `POST /simulate` and\n   * reports how the watched balances change and how much inner-call gas it\n   * burns.\n   *\n   * The result is a discriminated union on `status`:\n   * - `'ok'` — the simulation ran successfully; `deltas`/`gasUsed` are populated.\n   * - `'revert'` — the simulation ran but the transaction reverted on-chain. A\n   *   revert is a *successful simulation*, not a transport error, so it is\n   *   returned (HTTP 200) rather than thrown — mirroring how {@link compile}\n   *   returns `status: 'partial'` on a simulated revert.\n   * - `'error'` — the request was well-formed but the simulation could not be\n   *   set up or run (HTTP 422); `message` is intentionally generic.\n   *\n   * `bigint` amounts in the request (`value`, requirement `balance`/`allowance`)\n   * are serialised to decimal strings automatically.\n   *\n   * @param request - The raw transaction plus funding `requirements` and the\n   *   `trackedBalances` to watch.\n   * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n   * @throws {@link ComposeError} on network failures, HTTP 400 (malformed\n   *   input), 401/403 (auth), 404, 429, and 5xx.\n   */\n  readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\nconst bigintReplacer = (_key: string, value: unknown): unknown =>\n  typeof value === 'bigint' ? value.toString() : value;\n\nconst isNonNullObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null;\n\nconst parseBody = async <T>(res: Response, url: string): Promise<T> => {\n  const body = await res.json().catch((_) => null);\n  if (!isNonNullObject(body) || !('data' in body)) {\n    throw new ComposeError('UNKNOWN_ERROR', 'Unexpected response format', {\n      url,\n    });\n  }\n  return body.data as T;\n};\n\nconst parseCompileSuccessBody = async (\n  res: Response,\n  url: string,\n): Promise<ComposeCompileResult> => {\n  const data = await parseBody<ComposeCompileSuccessData>(res, url);\n  return { ...data, status: 'success' as const };\n};\n\nconst parsePartialBody = async (\n  res: Response,\n  url: string,\n): Promise<ComposeCompileResult> => {\n  const body = await res.json().catch((_) => null);\n  const envelope = parseCompilePartialEnvelope(body);\n  if (envelope === null) {\n    throw new ComposeError(\n      'UNKNOWN_ERROR',\n      'Unexpected partial response format',\n      { url },\n    );\n  }\n  // `data` is validated as an object by the schema; compose-spec owns its full\n  // shape as a hand-authored type, so we narrow it here rather than re-declaring\n  // that type as a schema. `error` is fully validated — no cast needed.\n  const data = envelope.data as unknown as ComposeCompilePartialData;\n  return { ...data, status: 'partial' as const, error: envelope.error };\n};\n\n// `/simulate` is un-enveloped: the discriminated body (`{ status, ... }`) is at\n// the top level, NOT wrapped in `{ data }` like `/compose`. So this reads the\n// body directly and validates it against the simulate union rather than reusing\n// `parseBody`.\nconst parseSimulateBody = async (\n  res: Response,\n  url: string,\n): Promise<SimulateResult> => {\n  const body = await res.json().catch((_) => null);\n  const result = parseSimulateResult(body);\n  if (result === null) {\n    throw new ComposeError(\n      'UNKNOWN_ERROR',\n      'Unexpected simulate response format',\n      { url },\n    );\n  }\n  return result;\n};\n\n/**\n * Creates a low-level Compose API client.\n *\n * @param options - Client configuration including the API base URL.\n * @returns A {@link ComposeClient} instance.\n */\nexport const createComposeClient = (\n  options: ComposeClientOptions,\n): ComposeClient => {\n  if (!options.baseUrl || !/^https?:\\/\\//i.test(options.baseUrl)) {\n    throw new ComposeError(\n      'VALIDATION_ERROR',\n      `Invalid baseUrl: expected an HTTP(S) URL, got \"${options.baseUrl}\"`,\n    );\n  }\n  const fetchFn = options.fetch ?? globalThis.fetch;\n  const base = options.baseUrl.replace(/\\/$/, '');\n\n  const trimmedApiKey = options.apiKey?.trim() || undefined;\n\n  const baseHeaders: Record<string, string> = {\n    Accept: 'application/json',\n    'x-lifi-composer-sdk': SDK_VERSION,\n    ...(trimmedApiKey ? { 'x-lifi-api-key': trimmedApiKey } : {}),\n  };\n\n  const getManifest = async (): Promise<ComposeManifest> => {\n    const url = `${base}/compose/manifest`;\n    let res: Response;\n    try {\n      res = await fetchFn(url, {\n        method: 'GET',\n        headers: { ...baseHeaders },\n      });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n    }\n    if (!res.ok) {\n      const body = await res.text();\n      throw errorFromHttpResponse(res.status, body, url);\n    }\n    return await parseBody<ComposeManifest>(res, url);\n  };\n\n  const compile = async (\n    request: ComposeCompileRequest,\n  ): Promise<ComposeCompileResult> => {\n    const url = `${base}/compose`;\n    let res: Response;\n    try {\n      res = await fetchFn(url, {\n        method: 'POST',\n        headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n        body: JSON.stringify(request, bigintReplacer),\n      });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n    }\n    if (res.status === 206) {\n      return await parsePartialBody(res, url);\n    }\n    if (!res.ok) {\n      const body = await res.text();\n      throw errorFromHttpResponse(res.status, body, url);\n    }\n    return await parseCompileSuccessBody(res, url);\n  };\n\n  const getZapPacks = async (\n    options?: GetZapPacksOptions,\n  ): Promise<readonly ZapPackOverview[]> => {\n    const params = new URLSearchParams();\n    if (options?.protocols !== undefined) {\n      // Backend expects a single comma-separated value, not repeated keys.\n      const raw = options.protocols;\n      const list = typeof raw === 'string' ? raw : raw.join(',');\n      params.set('protocols', list);\n    }\n    const qs = params.toString();\n    const url = `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`;\n    let res: Response;\n    try {\n      res = await fetchFn(url, {\n        method: 'GET',\n        headers: { ...baseHeaders },\n      });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n    }\n    if (!res.ok) {\n      const body = await res.text();\n      throw errorFromHttpResponse(res.status, body, url);\n    }\n    return await parseBody<readonly ZapPackOverview[]>(res, url);\n  };\n\n  const simulate = async (\n    request: SimulateRequest,\n  ): Promise<SimulateResult> => {\n    const url = `${base}/simulate`;\n    let res: Response;\n    try {\n      res = await fetchFn(url, {\n        method: 'POST',\n        headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n        body: JSON.stringify(request, bigintReplacer),\n      });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n    }\n    // Only 200 (carries `ok`/`revert`) and 422 (carries the `error` member of\n    // the union) have a discriminated body. 422 is deliberately intercepted\n    // here (not thrown as VALIDATION_ERROR) so callers get one exhaustive\n    // `switch (result.status)`. Every other status is a transport error and is\n    // thrown — including HTTP 400 (malformed input, no `status` body) and any\n    // unexpected 2xx.\n    if (res.status === 200 || res.status === 422) {\n      return await parseSimulateBody(res, url);\n    }\n    const body = await res.text();\n    throw errorFromHttpResponse(res.status, body, url);\n  };\n\n  return { getManifest, compile, getZapPacks, simulate };\n};\n"],"mappings":"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","names":["options"]}