{"version":3,"file":"build-url.cjs","names":[],"sources":["../../src/http/build-url.ts"],"sourcesContent":["/**\n * Matches a path that already carries its own scheme (`https:`, `blob:`), which\n * makes it an absolute URL rather than something to resolve against the base.\n */\nconst ABSOLUTE_URL = /^[a-z][a-z\\d+\\-.]*:/i;\n\n/** Split a path into its non-empty segments, dropping surrounding slashes. */\nfunction segments(path: string): string[] {\n    return path.split(\"/\").filter(Boolean);\n}\n\n/**\n * Whether `path` already starts with every segment of `prefix`.\n *\n * Compared segment by segment, so a `/api` prefix is not considered present in\n * `/api-keys` — a plain `startsWith` would swallow the resource.\n *\n * @param path - Segments of the request path.\n * @param prefix - Segments of the base path.\n * @returns Whether the prefix is already applied.\n */\nfunction startsWithSegments(path: string[], prefix: string[]): boolean {\n    if (prefix.length === 0 || prefix.length > path.length) return false;\n    return prefix.every((segment, index) => path[index] === segment);\n}\n\n/**\n * Resolve the origin a relative `baseURL` hangs off.\n *\n * A base such as `\"/api\"` is the shape you get behind a dev-server proxy or a\n * reverse proxy that serves app and API from one host. It only means something\n * in a browsing context, so this throws with the config to fix rather than\n * letting `new URL` report `Invalid base URL` from three frames deeper.\n *\n * @param baseURL - The base as the caller wrote it.\n * @returns The origin to resolve against.\n * @throws When there is no `location` to borrow an origin from.\n */\nfunction currentOrigin(baseURL: string): string {\n    const origin = globalThis.location?.origin;\n    if (!origin) {\n        throw new TypeError(\n            `createApiClient: baseURL \"${baseURL}\" is relative and there is no location to resolve it against. Pass an absolute URL (https://api.example.com) outside the browser.`,\n        );\n    }\n    return origin;\n}\n\n/** Options accepted by {@link buildApiUrl}. */\nexport interface BuildApiUrlOptions {\n    /**\n     * Path segment every request is nested under, such as `\"/api\"`. Joined\n     * after the path the `baseURL` already carries.\n     */\n    prefix?: string;\n    /** Query params to append. `undefined` and `null` values are skipped. */\n    params?: Record<string, string | number | boolean | undefined | null>;\n}\n\n/**\n * Join a base URL, an optional prefix and a request path into an absolute URL.\n *\n * `new URL(path, base)` on its own is wrong for an API client. It follows the\n * URL spec, where a path starting with `/` is absolute against the *origin* and\n * therefore discards whatever path the base carried: a client on\n * `https://api.example.com/api` asked for `/auth/login` reaches\n * `https://api.example.com/auth/login`, and every request 404s with nothing in\n * the config that looks wrong. This function resolves the path against the base\n * *path* instead, so the leading slash is a matter of taste rather than a\n * silent 404, and `baseURL` + `prefix` are interchangeable ways to say the same\n * thing.\n *\n * The prefix is applied at most once: a path that already opens with it — say\n * `\"/api/auth/login\"` under a `\"/api\"` prefix — is left alone, so a codebase\n * migrating to `prefix` can move its call sites one at a time. The check is per\n * segment, so `/api-keys` is not mistaken for an already-prefixed path.\n *\n * A path that is itself an absolute URL wins over all of this, which is how a\n * client reaches a second host (a signed upload endpoint, a CDN) without a\n * second client.\n *\n * @example\n * buildApiUrl(\"https://api.example.com\", \"/auth/login\", { prefix: \"/api\" });\n * // \"https://api.example.com/api/auth/login\"\n *\n * buildApiUrl(\"https://api.example.com/api\", \"auth/login\");\n * // \"https://api.example.com/api/auth/login\"\n *\n * @param baseURL - Absolute base URL, or a path relative to the current origin.\n * @param path - The request path, or an absolute URL to use as-is.\n * @param options - Optional prefix and query params.\n * @returns The absolute URL to fetch.\n * @throws When `baseURL` is relative and there is no `location` to resolve it.\n */\nexport function buildApiUrl(\n    baseURL: string,\n    path: string,\n    options: BuildApiUrlOptions = {},\n): string {\n    const { prefix, params } = options;\n    const url = ABSOLUTE_URL.test(path) ? new URL(path) : new URL(resolve(baseURL, path, prefix));\n\n    if (params) {\n        for (const [key, value] of Object.entries(params)) {\n            if (value !== undefined && value !== null) {\n                url.searchParams.set(key, String(value));\n            }\n        }\n    }\n    return url.toString();\n}\n\n/**\n * Build the absolute URL string for a path that is not already absolute.\n *\n * Kept separate from {@link buildApiUrl} so the query-param loop is not nested\n * inside the joining rules.\n *\n * @param baseURL - Absolute base URL, or a path relative to the current origin.\n * @param path - The request path.\n * @param prefix - Optional segment every request is nested under.\n * @returns The joined absolute URL.\n */\nfunction resolve(baseURL: string, path: string, prefix?: string): string {\n    const base = ABSOLUTE_URL.test(baseURL)\n        ? new URL(baseURL)\n        : new URL(baseURL, currentOrigin(baseURL));\n\n    const queryStart = path.indexOf(\"?\");\n    const rawPath = queryStart === -1 ? path : path.slice(0, queryStart);\n    const query = queryStart === -1 ? \"\" : path.slice(queryStart);\n\n    const basePath = [...segments(base.pathname), ...segments(prefix ?? \"\")];\n    const requested = segments(rawPath);\n    const joined = startsWithSegments(requested, basePath)\n        ? requested\n        : [...basePath, ...requested];\n\n    const trailing = rawPath.endsWith(\"/\") ? \"/\" : \"\";\n    const pathname = joined.length > 0 ? `/${joined.join(\"/\")}${trailing}` : \"/\";\n    return `${base.origin}${pathname}${query}`;\n}\n"],"mappings":"AAIA,IAAM,EAAe,uBAGrB,SAAS,EAAS,EAAwB,CACtC,OAAO,EAAK,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CACzC,CAYA,SAAS,EAAmB,EAAgB,EAA2B,CAEnE,OADI,EAAO,SAAW,GAAK,EAAO,OAAS,EAAK,OAAe,GACxD,EAAO,OAAO,EAAS,IAAU,EAAK,KAAW,CAAO,CACnE,CAcA,SAAS,EAAc,EAAyB,CAC5C,IAAM,EAAS,WAAW,UAAU,OACpC,GAAI,CAAC,EACD,MAAU,UACN,6BAA6B,EAAQ,kIACzC,EAEJ,OAAO,CACX,CAgDA,SAAgB,EACZ,EACA,EACA,EAA8B,CAAC,EACzB,CACN,GAAM,CAAE,SAAQ,UAAW,EACrB,EAAM,EAAa,KAAK,CAAI,EAAI,IAAI,IAAI,CAAI,EAAI,IAAI,IAAI,EAAQ,EAAS,EAAM,CAAM,CAAC,EAE5F,GAAI,EACK,IAAA,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EACxC,GAAiC,MACjC,EAAI,aAAa,IAAI,EAAK,OAAO,CAAK,CAAC,EAInD,OAAO,EAAI,SAAS,CACxB,CAaA,SAAS,EAAQ,EAAiB,EAAc,EAAyB,CACrE,IAAM,EAAO,EAAa,KAAK,CAAO,EAChC,IAAI,IAAI,CAAO,EACf,IAAI,IAAI,EAAS,EAAc,CAAO,CAAC,EAEvC,EAAa,EAAK,QAAQ,GAAG,EAC7B,EAAU,IAAe,GAAK,EAAO,EAAK,MAAM,EAAG,CAAU,EAC7D,EAAQ,IAAe,GAAK,GAAK,EAAK,MAAM,CAAU,EAEtD,EAAW,CAAC,GAAG,EAAS,EAAK,QAAQ,EAAG,GAAG,EAAS,GAAU,EAAE,CAAC,EACjE,EAAY,EAAS,CAAO,EAC5B,EAAS,EAAmB,EAAW,CAAQ,EAC/C,EACA,CAAC,GAAG,EAAU,GAAG,CAAS,EAE1B,EAAW,EAAQ,SAAS,GAAG,EAAI,IAAM,GACzC,EAAW,EAAO,OAAS,EAAI,IAAI,EAAO,KAAK,GAAG,IAAI,IAAa,IACzE,MAAO,GAAG,EAAK,SAAS,IAAW,GACvC"}