@api-wrappers/api-core 0.0.3 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +21 -9
- package/dist/index.cjs +90 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -49
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +59 -49
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +86 -24
- package/dist/index.mjs.map +1 -1
- package/docs/guides/error-handling.md +10 -10
- package/docs/guides/rest-requests.md +4 -0
- package/docs/reference/configuration.md +15 -4
- package/docs/reference/exports.md +8 -0
- package/package.json +21 -5
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/errors/ApiError.ts","../src/errors/RateLimitError.ts","../src/graphql/GraphQLRequestError.ts","../src/plugin/PluginManager.ts","../src/errors/TimeoutError.ts","../src/utils/buildUrl.ts","../src/utils/isPlainObject.ts","../src/transport/fetchTransport.ts","../src/utils/mergeHeaders.ts","../src/utils/resolveUrl.ts","../src/utils/sleep.ts","../src/client/BaseHttpClient.ts","../src/client/createClient.ts","../src/plugins/auth/authPlugin.ts","../src/plugins/cache/memoryStore.ts","../src/plugins/cache/cachePlugin.ts","../src/plugins/logger/loggerPlugin.ts","../src/plugins/rateLimit/rateLimitPlugin.ts","../src/plugins/retry/retryPlugin.ts","../src/plugins/timeout/timeoutPlugin.ts","../src/graphql/gql.ts"],"sourcesContent":["export class ApiError extends Error {\n\treadonly status: number;\n\treadonly responseBody: unknown;\n\toverride readonly cause: unknown;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tstatus: number,\n\t\tresponseBody?: unknown,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ApiError\";\n\t\tthis.status = status;\n\t\tthis.responseBody = responseBody;\n\t\tthis.cause = cause;\n\t}\n}\n","import { ApiError } from \"./ApiError\";\n\nexport class RateLimitError extends ApiError {\n\treadonly retryAfterMs: number | undefined;\n\n\tconstructor(retryAfterMs?: number, responseBody?: unknown, cause?: unknown) {\n\t\tsuper(\"Rate limit exceeded\", 429, responseBody, cause);\n\t\tthis.name = \"RateLimitError\";\n\t\tthis.retryAfterMs = retryAfterMs;\n\t}\n}\n","import { ApiError } from \"../errors/ApiError\";\nimport type { GraphQLErrorDetail } from \"./types\";\n\n/**\n * Thrown when a GraphQL server returns a well-formed HTTP 200 response that\n * contains a non-empty `errors` array.\n *\n * Extends {@link ApiError} so that code catching `ApiError` also catches\n * GraphQL-level failures. Callers that need to inspect the individual error\n * objects can narrow with `instanceof GraphQLRequestError` and read\n * `graphqlErrors`.\n *\n * When the server returns both `data` and `errors` (partial result), the\n * partial data is available on `partialData` but the error is still thrown —\n * callers must explicitly opt in to consuming partial results.\n *\n * @example\n * ```ts\n * import { GraphQLRequestError } from \"@api-wrappers/api-core\";\n *\n * try {\n * const data = await client.graphql<MyQuery>(\"/graphql\", { query: QUERY });\n * } catch (err) {\n * if (err instanceof GraphQLRequestError) {\n * for (const e of err.graphqlErrors) {\n * console.error(e.message, e.path);\n * }\n * }\n * }\n * ```\n */\nexport class GraphQLRequestError extends ApiError {\n\t/** The errors array from the GraphQL response envelope. */\n\treadonly graphqlErrors: readonly GraphQLErrorDetail[];\n\t/**\n\t * Partial `data` returned alongside `errors`, if any. `undefined` when\n\t * the server returned no `data` field.\n\t */\n\treadonly partialData: unknown;\n\n\tconstructor(\n\t\terrors: GraphQLErrorDetail[],\n\t\tpartialData?: unknown,\n\t\tcause?: unknown,\n\t) {\n\t\tconst message = errors.map((e) => e.message).join(\"; \");\n\t\t// Status 200: the HTTP request succeeded; the failure is at the\n\t\t// GraphQL application layer, not the transport layer.\n\t\tsuper(`GraphQL errors: ${message}`, 200, { errors }, cause);\n\t\tthis.name = \"GraphQLRequestError\";\n\t\tthis.graphqlErrors = errors;\n\t\tthis.partialData = partialData;\n\t}\n}\n","import type { LoggerInterface } from \"../client/types\";\nimport type { RequestContext } from \"../context/RequestContext\";\nimport type { ResponseContext } from \"../context/ResponseContext\";\nimport type { ApiPlugin } from \"./types\";\n\nexport class PluginManager {\n\tprivate readonly plugins: ApiPlugin[] = [];\n\tprivate readonly logger: LoggerInterface;\n\n\t/**\n\t * @param logger - Logger used when an `onError` handler itself throws.\n\t * Defaults to `console`. Pass a no-op object to silence all output.\n\t */\n\tconstructor(logger: LoggerInterface = console) {\n\t\tthis.logger = logger;\n\t}\n\n\tregister(plugin: ApiPlugin): void {\n\t\tif (plugin.enabled === false) return;\n\t\tthis.plugins.push(plugin);\n\t\t// Sort ascending so lower priority numbers run first in beforeRequest.\n\t\tthis.plugins.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));\n\t}\n\n\tgetAll(): readonly ApiPlugin[] {\n\t\treturn this.plugins;\n\t}\n\n\tasync setup(client: unknown): Promise<void> {\n\t\tfor (const plugin of this.plugins) {\n\t\t\tawait plugin.setup?.(client);\n\t\t}\n\t}\n\n\t/**\n\t * Runs `beforeRequest` in ascending priority order (lowest first).\n\t * Each plugin may return a mutated context.\n\t */\n\tasync beforeRequest(ctx: RequestContext): Promise<RequestContext> {\n\t\tlet current = ctx;\n\t\tfor (const plugin of this.plugins) {\n\t\t\ttry {\n\t\t\t\tconst result = await plugin.beforeRequest?.(current);\n\t\t\t\tif (result != null) current = result;\n\t\t\t} catch (err) {\n\t\t\t\tthrow wrapPluginError(plugin.name, \"beforeRequest\", err, current);\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Runs `afterResponse` in descending priority order (highest first).\n\t * Each plugin may return a mutated context.\n\t */\n\tasync afterResponse(ctx: ResponseContext): Promise<ResponseContext> {\n\t\tlet current = ctx;\n\t\tfor (const plugin of [...this.plugins].reverse()) {\n\t\t\ttry {\n\t\t\t\tconst result = await plugin.afterResponse?.(current);\n\t\t\t\tif (result != null) current = result;\n\t\t\t} catch (err) {\n\t\t\t\tthrow wrapPluginError(plugin.name, \"afterResponse\", err);\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Runs `onError` on all plugins in registration order. A plugin throwing\n\t * here is caught and logged via the configured logger but does not\n\t * interrupt other `onError` handlers.\n\t */\n\tasync onError(error: unknown, ctx: RequestContext): Promise<void> {\n\t\tfor (const plugin of this.plugins) {\n\t\t\ttry {\n\t\t\t\tawait plugin.onError?.(error, ctx);\n\t\t\t} catch (inner) {\n\t\t\t\tthis.logger.error(\n\t\t\t\t\t`[PluginManager] Plugin \"${plugin.name}\" threw inside onError:`,\n\t\t\t\t\tinner,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tfor (const plugin of [...this.plugins].reverse()) {\n\t\t\tawait plugin.dispose?.();\n\t\t}\n\t}\n}\n\nexport function getPluginErrorContext(\n\terror: unknown,\n): RequestContext | undefined {\n\tif (!error || typeof error !== \"object\") return undefined;\n\treturn (error as { requestContext?: RequestContext }).requestContext;\n}\n\nfunction wrapPluginError(\n\tname: string,\n\thook: string,\n\tcause: unknown,\n\trequestContext?: RequestContext,\n): Error {\n\tconst message = `Plugin \"${name}\" threw during \"${hook}\"`;\n\tconst err = new Error(message, { cause }) as Error & {\n\t\trequestContext?: RequestContext;\n\t};\n\terr.name = \"PluginError\";\n\terr.requestContext = requestContext;\n\treturn err;\n}\n","export class TimeoutError extends Error {\n\toverride readonly cause: unknown;\n\n\tconstructor(message = \"Request timed out\", cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = \"TimeoutError\";\n\t\tthis.cause = cause;\n\t}\n}\n","import type { QueryParams } from \"../types/common\";\n\n/**\n * Appends a query string to a URL. Skips nullish values and repeats keys for\n * array values so APIs like TMDB can accept `with_genres=1&with_genres=2`.\n */\nexport function buildUrl(base: string, query?: QueryParams): string {\n\tif (!query || Object.keys(query).length === 0) return base;\n\n\tconst params = new URLSearchParams();\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (value === undefined || value === null) continue;\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (item !== undefined && item !== null) {\n\t\t\t\t\tparams.append(key, String(item));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tparams.append(key, String(value));\n\t\t}\n\t}\n\n\tconst qs = params.toString();\n\tif (!qs) return base;\n\n\tconst separator = base.includes(\"?\")\n\t\t? base.endsWith(\"?\") || base.endsWith(\"&\")\n\t\t\t? \"\"\n\t\t\t: \"&\"\n\t\t: \"?\";\n\treturn `${base}${separator}${qs}`;\n}\n","export function isPlainObject(\n\tvalue: unknown,\n): value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst proto = Object.getPrototypeOf(value) as unknown;\n\treturn proto === Object.prototype || proto === null;\n}\n","import type { RequestContext } from \"../context/RequestContext\";\nimport { TimeoutError } from \"../errors/TimeoutError\";\nimport { buildUrl } from \"../utils/buildUrl\";\nimport { isPlainObject } from \"../utils/isPlainObject\";\nimport type { Transport } from \"./types\";\n\nconst defaultFetch: typeof globalThis.fetch = (input, init) => {\n\treturn globalThis.fetch(input, init);\n};\n\n/**\n * Creates a {@link Transport} backed by the provided `fetch` function.\n * Use this when you need a polyfill or a custom fetch interceptor:\n *\n * ```ts\n * import nodeFetch from \"node-fetch\";\n * createClient({ fetch: nodeFetch as typeof globalThis.fetch });\n * // — or set it directly on the transport:\n * const transport = createFetchTransport(nodeFetch as typeof globalThis.fetch);\n * ```\n */\nexport function createFetchTransport(\n\tfetchFn: typeof globalThis.fetch = defaultFetch,\n): Transport {\n\treturn {\n\t\tasync execute(ctx: RequestContext): Promise<Response> {\n\t\t\tconst url = buildUrl(ctx.url, ctx.query);\n\t\t\tconst init: RequestInit = {\n\t\t\t\tmethod: ctx.method,\n\t\t\t\theaders: ctx.headers,\n\t\t\t};\n\n\t\t\tconst hasBody =\n\t\t\t\tctx.body !== undefined && ctx.method !== \"GET\" && ctx.method !== \"HEAD\";\n\n\t\t\tif (hasBody) {\n\t\t\t\tinit.body = serializeRequestBody(ctx.body, ctx.headers);\n\t\t\t}\n\n\t\t\tif (ctx.timeoutMs !== undefined || ctx.signal) {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tlet timedOut = false;\n\t\t\t\tconst abortFromParent = () => controller.abort(ctx.signal?.reason);\n\t\t\t\tconst timer =\n\t\t\t\t\tctx.timeoutMs !== undefined\n\t\t\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\t\t\ttimedOut = true;\n\t\t\t\t\t\t\t\tcontroller.abort();\n\t\t\t\t\t\t\t}, ctx.timeoutMs)\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\tif (ctx.signal) {\n\t\t\t\t\tif (ctx.signal.aborted) {\n\t\t\t\t\t\tcontroller.abort(ctx.signal.reason);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tctx.signal.addEventListener(\"abort\", abortFromParent, {\n\t\t\t\t\t\t\tonce: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\treturn await fetchFn(url, { ...init, signal: controller.signal });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (timedOut && err instanceof Error && err.name === \"AbortError\") {\n\t\t\t\t\t\tthrow new TimeoutError(\n\t\t\t\t\t\t\t`Request timed out after ${ctx.timeoutMs}ms`,\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t} finally {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tctx.signal?.removeEventListener(\"abort\", abortFromParent);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn fetchFn(url, init);\n\t\t},\n\t};\n}\n\n/**\n * Default {@link Transport} backed by the global `fetch` API.\n *\n * Behaviour:\n * - Builds the final URL from `ctx.url` + `ctx.query` via {@link buildUrl}.\n * - Serialises `ctx.body` to JSON for non-GET/HEAD requests.\n * - Wires an `AbortController` when `ctx.timeoutMs` is set; throws\n * {@link TimeoutError} on abort.\n *\n * Replace this with a custom {@link Transport} in tests, or provide a custom\n * `fetch` function via {@link ClientConfig.fetch}.\n */\nexport const fetchTransport: Transport = createFetchTransport();\n\nfunction serializeRequestBody(\n\tbody: unknown,\n\theaders: Record<string, string>,\n): BodyInit {\n\tif (isBodyInit(body)) return body;\n\n\tconst contentType = headers[\"content-type\"] ?? \"\";\n\tif (\n\t\tisPlainObject(body) ||\n\t\tArray.isArray(body) ||\n\t\tcontentType.includes(\"json\")\n\t) {\n\t\treturn JSON.stringify(body);\n\t}\n\n\treturn String(body);\n}\n\nfunction isBodyInit(body: unknown): body is BodyInit {\n\tif (typeof body === \"string\") return true;\n\tif (body instanceof ArrayBuffer) return true;\n\tif (ArrayBuffer.isView(body)) return true;\n\tif (typeof Blob !== \"undefined\" && body instanceof Blob) return true;\n\tif (typeof FormData !== \"undefined\" && body instanceof FormData) return true;\n\tif (\n\t\ttypeof URLSearchParams !== \"undefined\" &&\n\t\tbody instanceof URLSearchParams\n\t) {\n\t\treturn true;\n\t}\n\tif (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n","/**\n * Merges header objects left to right. Keys are normalized to\n * lowercase so merging is case-insensitive. Later sources win.\n */\nexport function mergeHeaders(\n\t...sources: (Record<string, string> | undefined)[]\n): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\tfor (const source of sources) {\n\t\tif (!source) continue;\n\t\tfor (const [key, value] of Object.entries(source)) {\n\t\t\tresult[key.toLowerCase()] = value;\n\t\t}\n\t}\n\treturn result;\n}\n","/**\n * Joins a client base URL and request path without requiring callers to keep\n * slashes perfectly aligned. Absolute request URLs are returned unchanged.\n */\nexport function resolveUrl(baseUrl: string, path: string): string {\n\tif (/^[a-z][a-z\\d+\\-.]*:\\/\\//i.test(path)) return path;\n\n\tconst base = baseUrl.replace(/\\/+$/, \"\");\n\tconst next = path.replace(/^\\/+/, \"\");\n\n\treturn next ? `${base}/${next}` : base;\n}\n","export function sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { RequestContext } from \"../context/RequestContext\";\nimport type { ResponseContext } from \"../context/ResponseContext\";\nimport { ApiError } from \"../errors/ApiError\";\nimport { RateLimitError } from \"../errors/RateLimitError\";\nimport { GraphQLRequestError } from \"../graphql/GraphQLRequestError\";\nimport type { GraphQLRequestOptions, GraphQLResponse } from \"../graphql/types\";\nimport { getPluginErrorContext, PluginManager } from \"../plugin/PluginManager\";\nimport {\n\tcreateFetchTransport,\n\tfetchTransport,\n} from \"../transport/fetchTransport\";\nimport type { HttpMethod, QueryParams } from \"../types/common\";\nimport { mergeHeaders } from \"../utils/mergeHeaders\";\nimport { resolveUrl } from \"../utils/resolveUrl\";\nimport { sleep } from \"../utils/sleep\";\nimport type { ClientConfig } from \"./types\";\n\n/** Per-request options passed to {@link BaseHttpClient.request} and the convenience methods. */\nexport type ResponseType = \"auto\" | \"json\" | \"text\" | \"arrayBuffer\" | \"blob\";\n\nexport interface RequestOptions {\n\t/** HTTP method. Defaults to `\"GET\"`. */\n\tmethod?: HttpMethod;\n\t/**\n\t * Additional headers merged on top of `ClientConfig.defaultHeaders`.\n\t * These take precedence; `content-type: application/json` is always\n\t * present and is the lowest-priority default.\n\t */\n\theaders?: Record<string, string>;\n\t/** Request body. Serialised to JSON by {@link fetchTransport}. Ignored for GET and HEAD. */\n\tbody?: unknown;\n\t/**\n\t * Query string parameters appended to the URL. `undefined` values are\n\t * omitted. Numbers and booleans are coerced to strings. Array values are\n\t * emitted as repeated query parameters.\n\t */\n\tquery?: QueryParams;\n\t/** Optional caller-provided abort signal. Composes with `timeoutMs`. */\n\tsignal?: AbortSignal;\n\t/**\n\t * Per-request timeout override in milliseconds. Takes precedence over\n\t * `ClientConfig.timeoutMs`. Throws {@link TimeoutError} when exceeded.\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Explicit cache key used by {@link createCachePlugin}. When omitted the\n\t * plugin derives a key from the method, URL, and query string.\n\t */\n\tcacheKey?: string;\n\t/**\n\t * Arbitrary string tags attached to the request context. Plugins may use\n\t * these for cache invalidation, metrics grouping, or filtering.\n\t */\n\ttags?: string[];\n\t/**\n\t * Controls how the response body is parsed. Defaults to content-type based\n\t * parsing: JSON responses become objects, everything else becomes text.\n\t */\n\tresponseType?: ResponseType;\n\t/**\n\t * Optional response parser for non-2xx bodies. Defaults to `\"auto\"` so APIs\n\t * that return binary success payloads can still surface text/JSON errors.\n\t */\n\terrorResponseType?: ResponseType;\n}\n\nexport interface ApiResponse<T = unknown> {\n\tdata: T;\n\tresponse: Response;\n\trequest: RequestContext;\n\tmeta: Record<string, unknown>;\n}\n\nconst DEFAULT_RETRIABLE_STATUS_CODES = [429, 500, 502, 503, 504];\n\n/**\n * Core HTTP client. Manages the plugin lifecycle, retry loop, and transport\n * dispatch for all requests.\n *\n * Plugins are initialised lazily on the first call to {@link request} (or any\n * convenience method). Call {@link dispose} when the client is no longer\n * needed so plugins can release timers, connections, or cache handles.\n *\n * Extend this class to add domain-specific methods while keeping the plugin\n * and transport infrastructure intact.\n *\n * @example\n * ```ts\n * // Prefer createClient() in application code:\n * const client = createClient({ baseUrl: \"https://api.example.com/v1\" });\n *\n * // Or subclass for wrapper packages:\n * class MyApiClient extends BaseHttpClient {\n * getUser(id: string) { return this.get<User>(`/users/${id}`); }\n * }\n * ```\n */\nexport class BaseHttpClient {\n\tprotected readonly config: ClientConfig;\n\tprotected readonly pluginManager: PluginManager;\n\tprivate initialized = false;\n\tprivate initPromise: Promise<void> | undefined;\n\n\tconstructor(config: ClientConfig) {\n\t\tthis.config = config;\n\t\tthis.pluginManager = new PluginManager(config.logger);\n\t\tfor (const plugin of config.plugins ?? []) {\n\t\t\tthis.pluginManager.register(plugin);\n\t\t}\n\t}\n\n\t/** Initializes all plugins. Called lazily on first request. */\n\tasync init(): Promise<void> {\n\t\tif (this.initialized) return;\n\t\tthis.initPromise ??= this.pluginManager\n\t\t\t.setup(this)\n\t\t\t.then(() => {\n\t\t\t\tthis.initialized = true;\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tthis.initPromise = undefined;\n\t\t\t\tthrow err;\n\t\t\t});\n\t\tawait this.initPromise;\n\t}\n\n\t/** Disposes all plugins. Call when the client is no longer needed. */\n\tasync dispose(): Promise<void> {\n\t\tawait this.pluginManager.dispose();\n\t\tthis.initialized = false;\n\t\tthis.initPromise = undefined;\n\t}\n\n\t/**\n\t * Executes an HTTP request through the full plugin pipeline.\n\t *\n\t * Lifecycle per attempt:\n\t * 1. Build `RequestContext` with merged headers, query, and retry state.\n\t * 2. Run `beforeRequest` hooks (ascending priority). A plugin may set\n\t * `ctx.syntheticResponse` to skip the transport entirely (e.g. cache hit).\n\t * 3. Merge any `retry.*` meta written by {@link createRetryPlugin}.\n\t * 4. Call transport (skipped when `syntheticResponse` is set).\n\t * 5. Parse the response body (JSON or text).\n\t * 6. Run `afterResponse` hooks (descending priority).\n\t * 7. Retry on retriable status codes; throw on terminal failures.\n\t *\n\t * @param path - Path appended to `ClientConfig.baseUrl`. Should start with `/`.\n\t * @param options - Per-request overrides for method, headers, body, query, etc.\n\t * @returns The parsed response body cast to `T`.\n\t * @throws {@link ApiError} for non-2xx responses.\n\t * @throws {@link RateLimitError} for 429 responses.\n\t * @throws {@link TimeoutError} when `timeoutMs` is exceeded.\n\t */\n\tasync request<T = unknown>(\n\t\tpath: string,\n\t\toptions: RequestOptions = {},\n\t): Promise<T> {\n\t\tconst result = await this.requestWithResponse<T>(path, options);\n\t\treturn result.data;\n\t}\n\n\t/**\n\t * Executes a request and returns the parsed body plus the final response\n\t * context. Use this in wrappers that need response headers, status, or\n\t * plugin metadata while keeping the same error/retry behaviour as\n\t * {@link request}.\n\t */\n\tasync requestWithResponse<T = unknown>(\n\t\tpath: string,\n\t\toptions: RequestOptions = {},\n\t): Promise<ApiResponse<T>> {\n\t\tawait this.init();\n\n\t\tconst transport =\n\t\t\tthis.config.transport ??\n\t\t\t(this.config.fetch\n\t\t\t\t? createFetchTransport(this.config.fetch)\n\t\t\t\t: fetchTransport);\n\t\tconst retryCfg = this.config.retry;\n\t\t// These are `let` so createRetryPlugin can override them per-request via\n\t\t// ctx.meta after beforeRequest runs (see merge block below).\n\t\tlet maxAttempts = retryCfg?.maxAttempts ?? 1;\n\t\tlet baseDelay = retryCfg?.delayMs ?? 500;\n\t\tlet jitter = retryCfg?.jitter ?? true;\n\t\tlet retriableCodes =\n\t\t\tretryCfg?.retriableStatusCodes ?? DEFAULT_RETRIABLE_STATUS_CODES;\n\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\t\tconst baseCtx: RequestContext = {\n\t\t\t\turl: resolveUrl(this.config.baseUrl, path),\n\t\t\t\tmethod: options.method ?? \"GET\",\n\t\t\t\theaders: mergeHeaders(\n\t\t\t\t\t{ \"content-type\": \"application/json\" },\n\t\t\t\t\tthis.config.defaultHeaders,\n\t\t\t\t\toptions.headers,\n\t\t\t\t),\n\t\t\t\tbody: options.body,\n\t\t\t\tquery: options.query,\n\t\t\t\tsignal: options.signal,\n\t\t\t\tmeta: {},\n\t\t\t\tcacheKey: options.cacheKey,\n\t\t\t\ttags: options.tags,\n\t\t\t\tretryCount: maxAttempts - 1 - attempt,\n\t\t\t\tattempt,\n\t\t\t\ttimeoutMs: options.timeoutMs ?? this.config.timeoutMs,\n\t\t\t};\n\n\t\t\tlet ctx: RequestContext;\n\t\t\ttry {\n\t\t\t\tctx = await this.pluginManager.beforeRequest(baseCtx);\n\t\t\t} catch (err) {\n\t\t\t\tawait this.pluginManager.onError(\n\t\t\t\t\terr,\n\t\t\t\t\tgetPluginErrorContext(err) ?? baseCtx,\n\t\t\t\t);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\t// Merge per-request retry overrides written by createRetryPlugin.\n\t\t\t// Only keys explicitly set by the plugin are present, so unset keys\n\t\t\t// leave the config-level defaults untouched.\n\t\t\t// Because the for-loop condition re-evaluates `attempt < maxAttempts`\n\t\t\t// on every iteration, updating maxAttempts here takes effect\n\t\t\t// immediately for all remaining attempts.\n\t\t\tif (ctx.meta[\"retry.maxAttempts\"] !== undefined)\n\t\t\t\tmaxAttempts = ctx.meta[\"retry.maxAttempts\"] as number;\n\t\t\tif (ctx.meta[\"retry.delayMs\"] !== undefined)\n\t\t\t\tbaseDelay = ctx.meta[\"retry.delayMs\"] as number;\n\t\t\tif (ctx.meta[\"retry.jitter\"] !== undefined)\n\t\t\t\tjitter = ctx.meta[\"retry.jitter\"] as boolean;\n\t\t\tif (ctx.meta[\"retry.retriableStatusCodes\"] !== undefined)\n\t\t\t\tretriableCodes = ctx.meta[\"retry.retriableStatusCodes\"] as number[];\n\n\t\t\tlet rawResponse: Response;\n\t\t\tif (ctx.syntheticResponse) {\n\t\t\t\t// A plugin (e.g. cache) pre-populated the response — skip the\n\t\t\t\t// network entirely.\n\t\t\t\trawResponse = ctx.syntheticResponse;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\trawResponse = await transport.execute(ctx);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tawait this.pluginManager.onError(err, ctx);\n\t\t\t\t\tlastError = err;\n\n\t\t\t\t\tif (attempt < maxAttempts - 1) {\n\t\t\t\t\t\tawait this.waitForRetry(attempt, baseDelay, jitter);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst parsedBody = await parseBody(\n\t\t\t\trawResponse,\n\t\t\t\trawResponse.ok\n\t\t\t\t\t? options.responseType\n\t\t\t\t\t: (options.errorResponseType ?? \"auto\"),\n\t\t\t);\n\n\t\t\tlet resCtx: ResponseContext = {\n\t\t\t\trequest: ctx,\n\t\t\t\tresponse: rawResponse,\n\t\t\t\tparsedBody,\n\t\t\t\tmeta: {},\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tresCtx = await this.pluginManager.afterResponse(resCtx);\n\t\t\t} catch (err) {\n\t\t\t\tawait this.pluginManager.onError(err, ctx);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\tif (!rawResponse.ok) {\n\t\t\t\tconst shouldRetry =\n\t\t\t\t\tretriableCodes.includes(rawResponse.status) &&\n\t\t\t\t\tattempt < maxAttempts - 1;\n\n\t\t\t\tif (shouldRetry) {\n\t\t\t\t\tif (rawResponse.status === 429) {\n\t\t\t\t\t\tconst wait = readRetryAfterMs(rawResponse);\n\t\t\t\t\t\tawait this.waitForRetry(attempt, wait ?? baseDelay, false);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.waitForRetry(attempt, baseDelay, jitter);\n\t\t\t\t\t}\n\t\t\t\t\tlastError = normalizeHttpError(rawResponse, resCtx.parsedBody);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst err = normalizeHttpError(rawResponse, resCtx.parsedBody);\n\t\t\t\tawait this.pluginManager.onError(err, ctx);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdata: resCtx.parsedBody as T,\n\t\t\t\tresponse: resCtx.response,\n\t\t\t\trequest: resCtx.request,\n\t\t\t\tmeta: resCtx.meta,\n\t\t\t};\n\t\t}\n\n\t\tthrow lastError;\n\t}\n\n\t// ─── Convenience methods ────────────────────────────────────────────────────\n\t// Each method is a thin wrapper around request() that fixes the HTTP verb.\n\n\t/** Sends a GET request. The response body is not cached unless a cache plugin is registered. */\n\tget<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"GET\" });\n\t}\n\n\tpost<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: Omit<RequestOptions, \"method\" | \"body\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"POST\", body });\n\t}\n\n\tput<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: Omit<RequestOptions, \"method\" | \"body\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"PUT\", body });\n\t}\n\n\tpatch<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: Omit<RequestOptions, \"method\" | \"body\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"PATCH\", body });\n\t}\n\n\tdelete<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"DELETE\" });\n\t}\n\n\thead<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"HEAD\" });\n\t}\n\n\toptions<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"OPTIONS\" });\n\t}\n\n\t/**\n\t * Executes a GraphQL query or mutation against a single endpoint path.\n\t *\n\t * The request is a `POST` with `content-type: application/json` carrying\n\t * `{ query, variables?, operationName? }` as the body. It flows through\n\t * the full plugin lifecycle (beforeRequest → transport → afterResponse →\n\t * onError) and respects all retry configuration, exactly like REST calls.\n\t *\n\t * **Error handling:**\n\t * - HTTP-level failures (429, 500, timeout) throw the same error classes\n\t * as REST requests (`RateLimitError`, `ApiError`, `TimeoutError`).\n\t * - A successful HTTP 200 that contains a non-empty `errors` array throws\n\t * {@link GraphQLRequestError}, which extends `ApiError`.\n\t *\n\t * **Caching:**\n\t * The cache plugin skips `POST` requests by default. Pass an explicit\n\t * `cacheKey` in options to opt a specific operation into caching.\n\t *\n\t * @typeParam TData - Shape of the `data` field in the GraphQL response.\n\t * @typeParam TVariables - Shape of the `variables` object. Defaults to\n\t * `Record<string, unknown>`.\n\t * @param path - Endpoint path, e.g. `\"/graphql\"`. Appended to `baseUrl`.\n\t * @param options - Query document, variables, and optional per-request overrides.\n\t * @returns The `data` field from the GraphQL response envelope.\n\t * @throws {@link GraphQLRequestError} when `response.errors` is non-empty.\n\t * @throws {@link ApiError} / {@link RateLimitError} / {@link TimeoutError} on\n\t * HTTP-level failures.\n\t *\n\t * @example\n\t * ```ts\n\t * const data = await client.graphql<GetUserQuery, GetUserQueryVariables>(\n\t * \"/graphql\",\n\t * { query: GET_USER, variables: { id: \"123\" } },\n\t * );\n\t * ```\n\t */\n\tasync graphql<\n\t\tTData = unknown,\n\t\tTVariables extends object = Record<string, unknown>,\n\t>(path: string, options: GraphQLRequestOptions<TVariables>): Promise<TData> {\n\t\tconst {\n\t\t\tquery,\n\t\t\tvariables,\n\t\t\toperationName,\n\t\t\theaders,\n\t\t\tsignal,\n\t\t\ttimeoutMs,\n\t\t\tcacheKey,\n\t\t\ttags,\n\t\t} = options;\n\n\t\tconst envelope = await this.request<GraphQLResponse<TData>>(path, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tquery,\n\t\t\t\t...(variables !== undefined && { variables }),\n\t\t\t\t...(operationName !== undefined && { operationName }),\n\t\t\t},\n\t\t\theaders,\n\t\t\tsignal,\n\t\t\ttimeoutMs,\n\t\t\tcacheKey,\n\t\t\ttags,\n\t\t});\n\n\t\t// Surface GraphQL application-layer errors as a typed exception.\n\t\t// We throw even when partial data is present — callers who need\n\t\t// partial results can catch GraphQLRequestError and read .partialData.\n\t\tif (envelope.errors && envelope.errors.length > 0) {\n\t\t\tthrow new GraphQLRequestError(envelope.errors, envelope.data);\n\t\t}\n\n\t\t// data may be undefined if the server returned an empty response —\n\t\t// safe to cast because TData defaults to unknown.\n\t\treturn envelope.data as TData;\n\t}\n\n\tprivate async waitForRetry(\n\t\tattempt: number,\n\t\tbaseDelay: number,\n\t\tuseJitter: boolean,\n\t): Promise<void> {\n\t\t// Exponential backoff: delay * 2^attempt\n\t\tconst exponential = baseDelay * 2 ** attempt;\n\t\tconst ms = useJitter\n\t\t\t? exponential * (0.5 + Math.random() * 0.5)\n\t\t\t: exponential;\n\t\tawait sleep(Math.round(ms));\n\t}\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function parseBody(\n\tresponse: Response,\n\tresponseType: ResponseType = \"auto\",\n): Promise<unknown> {\n\tif (responseType === \"arrayBuffer\") return response.arrayBuffer();\n\tif (responseType === \"blob\") return response.blob();\n\n\tif (response.status === 204 || response.status === 205) return undefined;\n\tif (response.headers.get(\"content-length\") === \"0\") return undefined;\n\n\tconst text = await response.text();\n\tif (responseType === \"text\") return text;\n\tif (!text) return undefined;\n\n\tif (responseType === \"json\") return JSON.parse(text);\n\n\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\tif (contentType.includes(\"application/json\")) {\n\t\treturn JSON.parse(text);\n\t}\n\treturn text;\n}\n\nfunction normalizeHttpError(response: Response, body: unknown): ApiError {\n\tif (response.status === 429) {\n\t\treturn new RateLimitError(readRetryAfterMs(response), body);\n\t}\n\treturn new ApiError(\n\t\t`Request failed with status ${response.status}`,\n\t\tresponse.status,\n\t\tbody,\n\t);\n}\n\nfunction readRetryAfterMs(response: Response): number | undefined {\n\tconst raw = response.headers.get(\"retry-after\");\n\tif (!raw) return undefined;\n\n\tconst seconds = Number(raw);\n\tif (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);\n\n\tconst date = Date.parse(raw);\n\tif (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n\n\treturn undefined;\n}\n","import { BaseHttpClient } from \"./BaseHttpClient\";\nimport type { ClientConfig } from \"./types\";\n\n/**\n * Factory function that creates a {@link BaseHttpClient} from the given\n * config. Prefer this over `new BaseHttpClient(config)` in application code\n * so that the concrete class stays an implementation detail.\n *\n * @example\n * ```ts\n * const client = createClient({\n * baseUrl: \"https://api.example.com/v1\",\n * defaultHeaders: { \"x-api-key\": \"secret\" },\n * retry: { maxAttempts: 3, delayMs: 300 },\n * plugins: [createLoggerPlugin(), createCachePlugin({ ttlMs: 60_000 })],\n * });\n * ```\n */\nexport function createClient(config: ClientConfig): BaseHttpClient {\n\treturn new BaseHttpClient(config);\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { MaybePromise } from \"../../types/common\";\nimport type { AuthPluginOptions } from \"./types\";\n\ntype TokenInput =\n\t| string\n\t| (() => MaybePromise<string | null | undefined>)\n\t| AuthPluginOptions;\n\n/**\n * Adds an auth token header before each request. The token can be static or\n * loaded asynchronously per request, which covers wrappers with refreshable\n * access tokens.\n */\nexport function createAuthPlugin(input: TokenInput): ApiPlugin {\n\tconst options = normalizeOptions(input);\n\tconst headerName = (options.headerName ?? \"authorization\").toLowerCase();\n\tconst scheme = options.scheme === undefined ? \"Bearer\" : options.scheme;\n\n\treturn {\n\t\tname: \"auth\",\n\t\tpriority: 2,\n\n\t\tasync beforeRequest(ctx) {\n\t\t\tconst token = await options.getToken();\n\t\t\tif (!token) return ctx;\n\n\t\t\treturn {\n\t\t\t\t...ctx,\n\t\t\t\theaders: {\n\t\t\t\t\t...ctx.headers,\n\t\t\t\t\t[headerName]: scheme ? `${scheme} ${token}` : token,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction normalizeOptions(input: TokenInput): AuthPluginOptions {\n\tif (typeof input === \"string\") {\n\t\treturn { getToken: () => input };\n\t}\n\tif (typeof input === \"function\") {\n\t\treturn { getToken: input };\n\t}\n\treturn input;\n}\n","import type { CacheStore } from \"./types\";\n\ninterface CacheEntry {\n\tvalue: unknown;\n\texpiresAt: number | null;\n}\n\nexport class MemoryStore implements CacheStore {\n\tprivate readonly store = new Map<string, CacheEntry>();\n\n\tget(key: string): unknown | undefined {\n\t\tconst entry = this.store.get(key);\n\t\tif (!entry) return undefined;\n\t\tif (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n\t\t\tthis.store.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry.value;\n\t}\n\n\tset(key: string, value: unknown, ttlMs?: number): void {\n\t\tthis.store.set(key, {\n\t\t\tvalue,\n\t\t\texpiresAt: ttlMs != null ? Date.now() + ttlMs : null,\n\t\t});\n\t}\n\n\tdelete(key: string): void {\n\t\tthis.store.delete(key);\n\t}\n\n\tclear(): void {\n\t\tthis.store.clear();\n\t}\n}\n","import type { RequestContext } from \"../../context/RequestContext\";\nimport { MemoryStore } from \"./memoryStore\";\nimport type { CachePlugin, CachePluginOptions } from \"./types\";\n\nconst DEFAULT_CACHEABLE_METHODS = [\"GET\"] as const;\nconst CACHE_HIT_META_KEY = \"cache.hit\";\n\nexport function createCachePlugin(\n\toptions: CachePluginOptions = {},\n): CachePlugin {\n\tconst store = options.store ?? new MemoryStore();\n\tconst ttlMs = options.ttlMs;\n\tconst methods: string[] = options.methods ?? [...DEFAULT_CACHEABLE_METHODS];\n\tconst generateKey = options.generateKey ?? defaultCacheKey;\n\n\t// tag → Set<cacheKey>: populated during afterResponse, used by invalidateByTag.\n\tconst tagIndex = new Map<string, Set<string>>();\n\n\treturn {\n\t\tname: \"cache\",\n\t\tpriority: 20,\n\n\t\tasync beforeRequest(ctx) {\n\t\t\tif (!methods.includes(ctx.method)) return ctx;\n\n\t\t\tconst key = ctx.cacheKey ?? generateKey(ctx);\n\t\t\tconst cached = await store.get(key);\n\n\t\t\tif (cached !== undefined) {\n\t\t\t\t// Build a synthetic Response so the rest of the pipeline\n\t\t\t\t// (afterResponse, status checks) sees a uniform shape.\n\t\t\t\tconst syntheticResponse = new Response(JSON.stringify(cached), {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t});\n\n\t\t\t\t// Setting syntheticResponse tells BaseHttpClient to skip the\n\t\t\t\t// transport entirely and use this response directly.\n\t\t\t\treturn {\n\t\t\t\t\t...ctx,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\t...ctx.meta,\n\t\t\t\t\t\t[CACHE_HIT_META_KEY]: { key, data: cached },\n\t\t\t\t\t},\n\t\t\t\t\tsyntheticResponse,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...ctx,\n\t\t\t\tmeta: { ...ctx.meta, \"cache.key\": key },\n\t\t\t};\n\t\t},\n\n\t\tasync afterResponse(ctx) {\n\t\t\tconst hit = ctx.request.meta[CACHE_HIT_META_KEY] as\n\t\t\t\t| { key: string; data: unknown }\n\t\t\t\t| undefined;\n\n\t\t\tif (hit) {\n\t\t\t\treturn {\n\t\t\t\t\t...ctx,\n\t\t\t\t\tparsedBody: hit.data,\n\t\t\t\t\tmeta: { ...ctx.meta, \"cache.served\": true },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst key = ctx.request.meta[\"cache.key\"] as string | undefined;\n\t\t\tif (key && methods.includes(ctx.request.method) && ctx.response.ok) {\n\t\t\t\tawait store.set(key, ctx.parsedBody, ttlMs);\n\t\t\t\tctx.meta[\"cache.stored\"] = true;\n\n\t\t\t\t// Record tag → key associations for invalidateByTag.\n\t\t\t\tfor (const tag of ctx.request.tags ?? []) {\n\t\t\t\t\tif (!tagIndex.has(tag)) tagIndex.set(tag, new Set());\n\t\t\t\t\ttagIndex.get(tag)?.add(key);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn ctx;\n\t\t},\n\n\t\tasync invalidate(key: string): Promise<void> {\n\t\t\tawait store.delete(key);\n\t\t\t// Clean up any tag index entries pointing to this key.\n\t\t\tfor (const keys of tagIndex.values()) {\n\t\t\t\tkeys.delete(key);\n\t\t\t}\n\t\t},\n\n\t\tasync invalidateByTag(tag: string): Promise<void> {\n\t\t\tconst keys = tagIndex.get(tag);\n\t\t\tif (!keys || keys.size === 0) return;\n\t\t\tfor (const key of keys) {\n\t\t\t\tawait store.delete(key);\n\t\t\t\t// Remove this key from all other tag index entries too.\n\t\t\t\tfor (const otherKeys of tagIndex.values()) {\n\t\t\t\t\totherKeys.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttagIndex.delete(tag);\n\t\t},\n\t};\n}\n\nfunction defaultCacheKey(ctx: RequestContext): string {\n\tconst queryStr = ctx.query\n\t\t? new URLSearchParams(\n\t\t\t\tObject.fromEntries(\n\t\t\t\t\tObject.entries(ctx.query)\n\t\t\t\t\t\t.filter(([, v]) => v !== undefined)\n\t\t\t\t\t\t.map(([k, v]) => [k, String(v)]),\n\t\t\t\t),\n\t\t\t).toString()\n\t\t: \"\";\n\treturn `${ctx.method}:${ctx.url}${queryStr ? `?${queryStr}` : \"\"}`;\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { LoggerPluginOptions } from \"./types\";\n\n/**\n * Creates a plugin that logs request start, response status, and errors.\n *\n * Log lines are prefixed with `[api-core]` and include the HTTP method, URL,\n * attempt number (on `beforeRequest`), and status code (on `afterResponse`).\n *\n * Priority `10` means it runs _after_ auth or header-mutation plugins\n * (priority < 10) so the logged URL and headers reflect the final request,\n * but _before_ the cache plugin (priority `20`) so cache hits are still\n * visible in the log.\n *\n * @example\n * ```ts\n * createClient({\n * baseUrl: \"https://api.example.com\",\n * plugins: [createLoggerPlugin({ logRequest: true, logResponse: true })],\n * });\n * ```\n */\nexport function createLoggerPlugin(\n\toptions: LoggerPluginOptions = {},\n): ApiPlugin {\n\tconst {\n\t\tlogRequest = true,\n\t\tlogResponse = true,\n\t\tlogError = true,\n\t\tlogger = console,\n\t} = options;\n\n\treturn {\n\t\tname: \"logger\",\n\t\tpriority: 10,\n\n\t\tbeforeRequest(ctx) {\n\t\t\tif (logRequest) {\n\t\t\t\tlogger.info(`[api-core] --> ${ctx.method} ${ctx.url}`, {\n\t\t\t\t\tattempt: ctx.attempt,\n\t\t\t\t\tbody: ctx.body,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn ctx;\n\t\t},\n\n\t\tafterResponse(ctx) {\n\t\t\tif (logResponse) {\n\t\t\t\tlogger.info(\n\t\t\t\t\t`[api-core] <-- ${ctx.response.status} ${ctx.request.method} ${ctx.request.url}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn ctx;\n\t\t},\n\n\t\tonError(error, ctx) {\n\t\t\tif (logError) {\n\t\t\t\tlogger.error(`[api-core] ERR ${ctx.method} ${ctx.url}`, error);\n\t\t\t}\n\t\t},\n\t};\n}\n","import type { RequestContext } from \"../../context/RequestContext\";\nimport type { ApiPlugin } from \"../../plugin/types\";\nimport type { RateLimitPluginOptions } from \"./types\";\n\nconst RELEASE_META_KEY = \"rateLimit.release\";\n\ninterface QueueItem {\n\tresolve: (release: () => void) => void;\n}\n\n/**\n * Throttles request starts before they reach the transport. Supports\n * concurrency, minimum spacing, and fixed-window request budgets.\n */\nexport function createRateLimitPlugin(\n\toptions: RateLimitPluginOptions = {},\n): ApiPlugin {\n\tconst maxConcurrent = options.maxConcurrent ?? Number.POSITIVE_INFINITY;\n\tconst minTimeMs = options.minTimeMs ?? 0;\n\tconst maxRequestsPerInterval = options.maxRequestsPerInterval;\n\tconst intervalMs = options.intervalMs;\n\n\tif (maxConcurrent <= 0) {\n\t\tthrow new Error(\"maxConcurrent must be greater than 0\");\n\t}\n\tif (minTimeMs < 0) {\n\t\tthrow new Error(\"minTimeMs must be greater than or equal to 0\");\n\t}\n\tif (\n\t\t(maxRequestsPerInterval !== undefined || intervalMs !== undefined) &&\n\t\t(!maxRequestsPerInterval ||\n\t\t\tmaxRequestsPerInterval <= 0 ||\n\t\t\t!intervalMs ||\n\t\t\tintervalMs <= 0)\n\t) {\n\t\tthrow new Error(\n\t\t\t\"maxRequestsPerInterval and intervalMs must both be greater than 0\",\n\t\t);\n\t}\n\n\tconst queue: QueueItem[] = [];\n\tconst starts: number[] = [];\n\tlet active = 0;\n\tlet lastStartAt = 0;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\n\tconst processQueue = () => {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\n\t\twhile (queue.length > 0) {\n\t\t\tconst now = Date.now();\n\t\t\tpruneStarts(now);\n\n\t\t\tif (active >= maxConcurrent) return;\n\n\t\t\tconst waitMs = getWaitMs(now);\n\t\t\tif (waitMs > 0) {\n\t\t\t\ttimer = setTimeout(processQueue, waitMs);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst item = queue.shift();\n\t\t\tif (!item) return;\n\n\t\t\tactive++;\n\t\t\tlastStartAt = now;\n\t\t\tstarts.push(now);\n\n\t\t\tlet released = false;\n\t\t\titem.resolve(() => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tactive--;\n\t\t\t\tprocessQueue();\n\t\t\t});\n\t\t}\n\t};\n\n\tconst acquire = () =>\n\t\tnew Promise<() => void>((resolve) => {\n\t\t\tqueue.push({ resolve });\n\t\t\tprocessQueue();\n\t\t});\n\n\tconst release = (ctx: RequestContext) => {\n\t\tconst releaseFn = ctx.meta[RELEASE_META_KEY] as (() => void) | undefined;\n\t\tif (!releaseFn) return;\n\t\tdelete ctx.meta[RELEASE_META_KEY];\n\t\treleaseFn();\n\t};\n\n\tconst pruneStarts = (now: number) => {\n\t\tif (!intervalMs) return;\n\t\twhile (starts.length > 0 && now - (starts[0] ?? 0) >= intervalMs) {\n\t\t\tstarts.shift();\n\t\t}\n\t};\n\n\tconst getWaitMs = (now: number): number => {\n\t\tconst spacingWait = Math.max(0, lastStartAt + minTimeMs - now);\n\t\tif (!maxRequestsPerInterval || !intervalMs) return spacingWait;\n\n\t\tif (starts.length < maxRequestsPerInterval) return spacingWait;\n\n\t\tconst oldest = starts[0] ?? now;\n\t\tconst intervalWait = Math.max(0, oldest + intervalMs - now);\n\t\treturn Math.max(spacingWait, intervalWait);\n\t};\n\n\treturn {\n\t\tname: \"rate-limit\",\n\t\tpriority: 1,\n\n\t\tasync beforeRequest(ctx) {\n\t\t\tconst releaseFn = await acquire();\n\t\t\treturn {\n\t\t\t\t...ctx,\n\t\t\t\tmeta: { ...ctx.meta, [RELEASE_META_KEY]: releaseFn },\n\t\t\t};\n\t\t},\n\n\t\tafterResponse(ctx) {\n\t\t\trelease(ctx.request);\n\t\t\treturn ctx;\n\t\t},\n\n\t\tonError(_error, ctx) {\n\t\t\trelease(ctx);\n\t\t},\n\t};\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { RetryPluginOptions } from \"./types\";\n\n/**\n * Writes retry configuration into request context meta so the\n * BaseHttpClient retry loop can read it. Use this when you need\n * per-request retry overrides rather than global ClientConfig.retry.\n */\nexport function createRetryPlugin(options: RetryPluginOptions = {}): ApiPlugin {\n\treturn {\n\t\tname: \"retry\",\n\t\tpriority: 5,\n\n\t\tbeforeRequest(ctx) {\n\t\t\t// Only write keys for values the caller explicitly provided.\n\t\t\t// Unset options fall through to ClientConfig.retry defaults inside\n\t\t\t// BaseHttpClient, so the plugin does not need to supply fallbacks.\n\t\t\tif (options.maxAttempts !== undefined)\n\t\t\t\tctx.meta[\"retry.maxAttempts\"] = options.maxAttempts;\n\t\t\tif (options.delayMs !== undefined)\n\t\t\t\tctx.meta[\"retry.delayMs\"] = options.delayMs;\n\t\t\tif (options.jitter !== undefined)\n\t\t\t\tctx.meta[\"retry.jitter\"] = options.jitter;\n\t\t\tif (options.retriableStatusCodes !== undefined)\n\t\t\t\tctx.meta[\"retry.retriableStatusCodes\"] = options.retriableStatusCodes;\n\t\t\treturn ctx;\n\t\t},\n\t};\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { TimeoutPluginOptions } from \"./types\";\n\n/**\n * Sets `ctx.timeoutMs` on every request so all requests made by this client\n * abort after the configured duration. The actual abort and\n * {@link TimeoutError} are handled by {@link fetchTransport}.\n *\n * Priority `1` ensures the timeout is stamped before any other plugin (e.g.\n * logger, cache) runs — plugins that read `ctx.timeoutMs` will always see it.\n * Use a `beforeRequest` hook with a lower priority to override per-request.\n *\n * Prefer `ClientConfig.timeoutMs` for a static global timeout. Use this\n * plugin when you need to set or change the timeout through the plugin\n * pipeline (e.g. from environment config loaded asynchronously in `setup`).\n *\n * @example\n * ```ts\n * createClient({\n * baseUrl: \"https://api.example.com\",\n * plugins: [createTimeoutPlugin({ timeoutMs: 5_000 })],\n * });\n * ```\n */\nexport function createTimeoutPlugin(options: TimeoutPluginOptions): ApiPlugin {\n\treturn {\n\t\tname: \"timeout\",\n\t\tpriority: 1,\n\n\t\tbeforeRequest(ctx) {\n\t\t\treturn { ...ctx, timeoutMs: options.timeoutMs };\n\t\t},\n\t};\n}\n","/**\n * Lightweight GraphQL template tag.\n *\n * This intentionally does not parse into a DocumentNode. It preserves the\n * query string while still giving GraphQL-aware tooling a familiar `gql` tag.\n */\nexport function gql(\n\tchunks: TemplateStringsArray,\n\t...values: unknown[]\n): string {\n\tconst source = chunks.reduce(\n\t\t(source, chunk, index) =>\n\t\t\t`${source}${chunk}${index in values ? String(values[index]) : \"\"}`,\n\t\t\"\",\n\t);\n\n\treturn dedupeFragmentDefinitions(source);\n}\n\nfunction dedupeFragmentDefinitions(source: string): string {\n\tconst seen = new Set<string>();\n\tconst fragmentPattern =\n\t\t/\\bfragment\\s+([_A-Za-z][_0-9A-Za-z]*)\\s+on\\s+[_A-Za-z][_0-9A-Za-z]*/g;\n\n\tlet result = \"\";\n\tlet cursor = 0;\n\tlet match = fragmentPattern.exec(source);\n\n\twhile (match) {\n\t\tconst name = match[1];\n\t\tif (!name) {\n\t\t\tmatch = fragmentPattern.exec(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst bodyStart = source.indexOf(\"{\", fragmentPattern.lastIndex);\n\t\tif (bodyStart === -1) {\n\t\t\tmatch = fragmentPattern.exec(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst bodyEnd = findMatchingBrace(source, bodyStart);\n\t\tif (bodyEnd === -1) {\n\t\t\tmatch = fragmentPattern.exec(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst fragmentStart = match.index;\n\t\tconst fragmentEnd = consumeTrailingWhitespace(source, bodyEnd + 1);\n\n\t\tif (!seen.has(name)) {\n\t\t\tseen.add(name);\n\t\t\tresult += source.slice(cursor, fragmentEnd);\n\t\t} else {\n\t\t\tresult += source.slice(cursor, fragmentStart);\n\t\t}\n\n\t\tcursor = fragmentEnd;\n\t\tfragmentPattern.lastIndex = fragmentEnd;\n\t\tmatch = fragmentPattern.exec(source);\n\t}\n\n\treturn result + source.slice(cursor);\n}\n\nfunction findMatchingBrace(source: string, openBraceIndex: number): number {\n\tlet depth = 0;\n\n\tfor (let index = openBraceIndex; index < source.length; index++) {\n\t\tconst char = source[index];\n\t\tif (char === \"{\") depth++;\n\t\tif (char === \"}\") depth--;\n\t\tif (depth === 0) return index;\n\t}\n\n\treturn -1;\n}\n\nfunction consumeTrailingWhitespace(source: string, index: number): number {\n\tlet next = index;\n\twhile (next < source.length && /\\s/.test(source[next] ?? \"\")) {\n\t\tnext++;\n\t}\n\treturn next;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/errors/ApiError.ts","../src/errors/RateLimitError.ts","../src/graphql/GraphQLRequestError.ts","../src/plugin/PluginManager.ts","../src/errors/TimeoutError.ts","../src/utils/buildUrl.ts","../src/utils/isJsonContentType.ts","../src/utils/isPlainObject.ts","../src/transport/fetchTransport.ts","../src/utils/mergeHeaders.ts","../src/utils/normalizeRetryMaxAttempts.ts","../src/utils/resolveUrl.ts","../src/utils/sleep.ts","../src/client/BaseHttpClient.ts","../src/client/createClient.ts","../src/errors/guards.ts","../src/plugins/auth/authPlugin.ts","../src/plugins/cache/memoryStore.ts","../src/plugins/cache/cachePlugin.ts","../src/plugins/logger/loggerPlugin.ts","../src/plugins/rateLimit/rateLimitPlugin.ts","../src/plugins/retry/retryPlugin.ts","../src/plugins/timeout/timeoutPlugin.ts","../src/graphql/gql.ts"],"sourcesContent":["export class ApiError extends Error {\n\treadonly status: number;\n\treadonly responseBody: unknown;\n\toverride readonly cause: unknown;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tstatus: number,\n\t\tresponseBody?: unknown,\n\t\tcause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ApiError\";\n\t\tthis.status = status;\n\t\tthis.responseBody = responseBody;\n\t\tthis.cause = cause;\n\t}\n}\n","import { ApiError } from \"./ApiError\";\n\nexport class RateLimitError extends ApiError {\n\treadonly retryAfterMs: number | undefined;\n\n\tconstructor(retryAfterMs?: number, responseBody?: unknown, cause?: unknown) {\n\t\tsuper(\"Rate limit exceeded\", 429, responseBody, cause);\n\t\tthis.name = \"RateLimitError\";\n\t\tthis.retryAfterMs = retryAfterMs;\n\t}\n}\n","import { ApiError } from \"../errors/ApiError\";\nimport type { GraphQLErrorDetail } from \"./types\";\n\n/**\n * Thrown when a GraphQL server returns a well-formed HTTP 200 response that\n * contains a non-empty `errors` array.\n *\n * Extends {@link ApiError} so that code catching `ApiError` also catches\n * GraphQL-level failures. Callers that need to inspect the individual error\n * objects can narrow with `instanceof GraphQLRequestError` and read\n * `graphqlErrors`.\n *\n * When the server returns both `data` and `errors` (partial result), the\n * partial data is available on `partialData` but the error is still thrown —\n * callers must explicitly opt in to consuming partial results.\n *\n * @example\n * ```ts\n * import { GraphQLRequestError } from \"@api-wrappers/api-core\";\n *\n * try {\n * const data = await client.graphql<MyQuery>(\"/graphql\", { query: QUERY });\n * } catch (err) {\n * if (err instanceof GraphQLRequestError) {\n * for (const e of err.graphqlErrors) {\n * console.error(e.message, e.path);\n * }\n * }\n * }\n * ```\n */\nexport class GraphQLRequestError extends ApiError {\n\t/** The errors array from the GraphQL response envelope. */\n\treadonly graphqlErrors: readonly GraphQLErrorDetail[];\n\t/**\n\t * Partial `data` returned alongside `errors`, if any. `undefined` when\n\t * the server returned no `data` field.\n\t */\n\treadonly partialData: unknown;\n\n\tconstructor(\n\t\terrors: GraphQLErrorDetail[],\n\t\tpartialData?: unknown,\n\t\tcause?: unknown,\n\t) {\n\t\tconst message = errors.map((e) => e.message).join(\"; \");\n\t\t// Status 200: the HTTP request succeeded; the failure is at the\n\t\t// GraphQL application layer, not the transport layer.\n\t\tsuper(`GraphQL errors: ${message}`, 200, { errors }, cause);\n\t\tthis.name = \"GraphQLRequestError\";\n\t\tthis.graphqlErrors = errors;\n\t\tthis.partialData = partialData;\n\t}\n}\n","import type { LoggerInterface } from \"../client/types\";\nimport type { RequestContext } from \"../context/RequestContext\";\nimport type { ResponseContext } from \"../context/ResponseContext\";\nimport type { ApiPlugin } from \"./types\";\n\nexport class PluginManager {\n\tprivate readonly plugins: ApiPlugin[] = [];\n\tprivate readonly logger: LoggerInterface;\n\n\t/**\n\t * @param logger - Logger used when an `onError` handler itself throws.\n\t * Defaults to `console`. Pass a no-op object to silence all output.\n\t */\n\tconstructor(logger: LoggerInterface = console) {\n\t\tthis.logger = logger;\n\t}\n\n\tregister(plugin: ApiPlugin): void {\n\t\tif (plugin.enabled === false) return;\n\t\tthis.plugins.push(plugin);\n\t\t// Sort ascending so lower priority numbers run first in beforeRequest.\n\t\tthis.plugins.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));\n\t}\n\n\tgetAll(): readonly ApiPlugin[] {\n\t\treturn this.plugins;\n\t}\n\n\tasync setup(client: unknown): Promise<void> {\n\t\tfor (const plugin of this.plugins) {\n\t\t\tawait plugin.setup?.(client);\n\t\t}\n\t}\n\n\t/**\n\t * Runs `beforeRequest` in ascending priority order (lowest first).\n\t * Each plugin may return a mutated context.\n\t */\n\tasync beforeRequest(ctx: RequestContext): Promise<RequestContext> {\n\t\tlet current = ctx;\n\t\tfor (const plugin of this.plugins) {\n\t\t\ttry {\n\t\t\t\tconst result = await plugin.beforeRequest?.(current);\n\t\t\t\tif (result != null) current = result;\n\t\t\t} catch (err) {\n\t\t\t\tthrow wrapPluginError(plugin.name, \"beforeRequest\", err, current);\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Runs `afterResponse` in descending priority order (highest first).\n\t * Each plugin may return a mutated context.\n\t */\n\tasync afterResponse(ctx: ResponseContext): Promise<ResponseContext> {\n\t\tlet current = ctx;\n\t\tfor (const plugin of [...this.plugins].reverse()) {\n\t\t\ttry {\n\t\t\t\tconst result = await plugin.afterResponse?.(current);\n\t\t\t\tif (result != null) current = result;\n\t\t\t} catch (err) {\n\t\t\t\tthrow wrapPluginError(plugin.name, \"afterResponse\", err);\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Runs `onError` on all plugins in registration order. A plugin throwing\n\t * here is caught and logged via the configured logger but does not\n\t * interrupt other `onError` handlers.\n\t */\n\tasync onError(error: unknown, ctx: RequestContext): Promise<void> {\n\t\tfor (const plugin of this.plugins) {\n\t\t\ttry {\n\t\t\t\tawait plugin.onError?.(error, ctx);\n\t\t\t} catch (inner) {\n\t\t\t\tthis.logger.error(\n\t\t\t\t\t`[PluginManager] Plugin \"${plugin.name}\" threw inside onError:`,\n\t\t\t\t\tinner,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tfor (const plugin of [...this.plugins].reverse()) {\n\t\t\tawait plugin.dispose?.();\n\t\t}\n\t}\n}\n\nexport function getPluginErrorContext(\n\terror: unknown,\n): RequestContext | undefined {\n\tif (!error || typeof error !== \"object\") return undefined;\n\treturn (error as { requestContext?: RequestContext }).requestContext;\n}\n\nfunction wrapPluginError(\n\tname: string,\n\thook: string,\n\tcause: unknown,\n\trequestContext?: RequestContext,\n): Error {\n\tconst message = `Plugin \"${name}\" threw during \"${hook}\"`;\n\tconst err = new Error(message, { cause }) as Error & {\n\t\trequestContext?: RequestContext;\n\t};\n\terr.name = \"PluginError\";\n\terr.requestContext = requestContext;\n\treturn err;\n}\n","export class TimeoutError extends Error {\n\toverride readonly cause: unknown;\n\n\tconstructor(message = \"Request timed out\", cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = \"TimeoutError\";\n\t\tthis.cause = cause;\n\t}\n}\n","import type { QueryParams } from \"../types/common\";\n\n/**\n * Appends a query string to a URL. Skips nullish values and repeats keys for\n * array values so APIs like TMDB can accept `with_genres=1&with_genres=2`.\n */\nexport function buildUrl(base: string, query?: QueryParams): string {\n\tif (!query || Object.keys(query).length === 0) return base;\n\n\tconst params = new URLSearchParams();\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (value === undefined || value === null) continue;\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (item !== undefined && item !== null) {\n\t\t\t\t\tparams.append(key, String(item));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tparams.append(key, String(value));\n\t\t}\n\t}\n\n\tconst qs = params.toString();\n\tif (!qs) return base;\n\n\tconst separator = base.includes(\"?\")\n\t\t? base.endsWith(\"?\") || base.endsWith(\"&\")\n\t\t\t? \"\"\n\t\t\t: \"&\"\n\t\t: \"?\";\n\treturn `${base}${separator}${qs}`;\n}\n","export function isJsonContentType(\n\tcontentType: string | null | undefined,\n): boolean {\n\tconst mediaType = contentType?.split(\";\", 1)[0]?.trim().toLowerCase();\n\n\treturn (\n\t\tmediaType === \"application/json\" || mediaType?.endsWith(\"+json\") === true\n\t);\n}\n","export function isPlainObject(\n\tvalue: unknown,\n): value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst proto = Object.getPrototypeOf(value) as unknown;\n\treturn proto === Object.prototype || proto === null;\n}\n","import type { RequestContext } from \"../context/RequestContext\";\nimport { TimeoutError } from \"../errors/TimeoutError\";\nimport { buildUrl } from \"../utils/buildUrl\";\nimport { isJsonContentType } from \"../utils/isJsonContentType\";\nimport { isPlainObject } from \"../utils/isPlainObject\";\nimport type { FetchLike, Transport } from \"./types\";\n\nconst defaultFetch: FetchLike = (input, init) => {\n\treturn globalThis.fetch(input, init);\n};\n\n/**\n * Creates a {@link Transport} backed by the provided `fetch` function.\n * Use this when you need a polyfill or a custom fetch interceptor:\n *\n * ```ts\n * import nodeFetch from \"node-fetch\";\n * createClient({ fetch: nodeFetch as FetchLike });\n * // — or set it directly on the transport:\n * const transport = createFetchTransport(nodeFetch as FetchLike);\n * ```\n */\nexport function createFetchTransport(\n\tfetchFn: FetchLike = defaultFetch,\n): Transport {\n\treturn {\n\t\tasync execute(ctx: RequestContext): Promise<Response> {\n\t\t\tconst url = buildUrl(ctx.url, ctx.query);\n\t\t\tconst init: RequestInit = {\n\t\t\t\tmethod: ctx.method,\n\t\t\t\theaders: ctx.headers,\n\t\t\t};\n\n\t\t\tconst hasBody =\n\t\t\t\tctx.body !== undefined && ctx.method !== \"GET\" && ctx.method !== \"HEAD\";\n\n\t\t\tif (hasBody) {\n\t\t\t\tinit.body = serializeRequestBody(ctx.body, ctx.headers);\n\t\t\t}\n\n\t\t\tif (ctx.timeoutMs !== undefined || ctx.signal) {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tlet timedOut = false;\n\t\t\t\tconst abortFromParent = () => controller.abort(ctx.signal?.reason);\n\t\t\t\tconst timer =\n\t\t\t\t\tctx.timeoutMs !== undefined\n\t\t\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\t\t\ttimedOut = true;\n\t\t\t\t\t\t\t\tcontroller.abort();\n\t\t\t\t\t\t\t}, ctx.timeoutMs)\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\tif (ctx.signal) {\n\t\t\t\t\tif (ctx.signal.aborted) {\n\t\t\t\t\t\tcontroller.abort(ctx.signal.reason);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tctx.signal.addEventListener(\"abort\", abortFromParent, {\n\t\t\t\t\t\t\tonce: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\treturn await fetchFn(url, { ...init, signal: controller.signal });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (timedOut && err instanceof Error && err.name === \"AbortError\") {\n\t\t\t\t\t\tthrow new TimeoutError(\n\t\t\t\t\t\t\t`Request timed out after ${ctx.timeoutMs}ms`,\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t} finally {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tctx.signal?.removeEventListener(\"abort\", abortFromParent);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn fetchFn(url, init);\n\t\t},\n\t};\n}\n\n/**\n * Default {@link Transport} backed by the global `fetch` API.\n *\n * Behaviour:\n * - Builds the final URL from `ctx.url` + `ctx.query` via {@link buildUrl}.\n * - Serialises `ctx.body` to JSON for non-GET/HEAD requests.\n * - Wires an `AbortController` when `ctx.timeoutMs` is set; throws\n * {@link TimeoutError} on abort.\n *\n * Replace this with a custom {@link Transport} in tests, or provide a custom\n * `fetch` function via {@link ClientConfig.fetch}.\n */\nexport const fetchTransport: Transport = createFetchTransport();\n\nfunction serializeRequestBody(\n\tbody: unknown,\n\theaders: Record<string, string>,\n): BodyInit {\n\tif (isBodyInit(body)) return body;\n\n\tif (\n\t\tisPlainObject(body) ||\n\t\tArray.isArray(body) ||\n\t\tisJsonContentType(headers[\"content-type\"])\n\t) {\n\t\treturn JSON.stringify(body);\n\t}\n\n\treturn String(body);\n}\n\nfunction isBodyInit(body: unknown): body is BodyInit {\n\tif (typeof body === \"string\") return true;\n\tif (body instanceof ArrayBuffer) return true;\n\tif (ArrayBuffer.isView(body)) return true;\n\tif (typeof Blob !== \"undefined\" && body instanceof Blob) return true;\n\tif (typeof FormData !== \"undefined\" && body instanceof FormData) return true;\n\tif (\n\t\ttypeof URLSearchParams !== \"undefined\" &&\n\t\tbody instanceof URLSearchParams\n\t) {\n\t\treturn true;\n\t}\n\tif (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n","import type { HeaderInput } from \"../types/common\";\n\n/**\n * Merges header objects left to right. Keys are normalized to\n * lowercase so merging is case-insensitive. Later sources win.\n */\nexport function mergeHeaders(\n\t...sources: (HeaderInput | undefined)[]\n): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\tfor (const source of sources) {\n\t\tif (!source) continue;\n\n\t\tif (isHeaders(source)) {\n\t\t\tsource.forEach((value, key) => {\n\t\t\t\tresult[key.toLowerCase()] = value;\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (Array.isArray(source)) {\n\t\t\tfor (const [key, value] of source) {\n\t\t\t\tresult[key.toLowerCase()] = value;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(source)) {\n\t\t\tresult[key.toLowerCase()] = String(value);\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction isHeaders(source: HeaderInput): source is Headers {\n\treturn typeof Headers !== \"undefined\" && source instanceof Headers;\n}\n","export function normalizeRetryMaxAttempts(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) return 1;\n\treturn Math.max(1, Math.floor(value));\n}\n","/**\n * Joins a client base URL and request path without requiring callers to keep\n * slashes perfectly aligned. Absolute request URLs are returned unchanged.\n */\nexport function resolveUrl(baseUrl: string, path: string): string {\n\tif (/^[a-z][a-z\\d+\\-.]*:\\/\\//i.test(path)) return path;\n\n\tconst base = baseUrl.replace(/\\/+$/, \"\");\n\tconst next = path.replace(/^\\/+/, \"\");\n\n\treturn next ? `${base}/${next}` : base;\n}\n","export function sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { RequestContext } from \"../context/RequestContext\";\nimport type { ResponseContext } from \"../context/ResponseContext\";\nimport { ApiError } from \"../errors/ApiError\";\nimport { RateLimitError } from \"../errors/RateLimitError\";\nimport { GraphQLRequestError } from \"../graphql/GraphQLRequestError\";\nimport type { GraphQLRequestOptions, GraphQLResponse } from \"../graphql/types\";\nimport { getPluginErrorContext, PluginManager } from \"../plugin/PluginManager\";\nimport {\n\tcreateFetchTransport,\n\tfetchTransport,\n} from \"../transport/fetchTransport\";\nimport type { HeaderInput, HttpMethod, QueryParams } from \"../types/common\";\nimport { isJsonContentType } from \"../utils/isJsonContentType\";\nimport { mergeHeaders } from \"../utils/mergeHeaders\";\nimport { normalizeRetryMaxAttempts } from \"../utils/normalizeRetryMaxAttempts\";\nimport { resolveUrl } from \"../utils/resolveUrl\";\nimport { sleep } from \"../utils/sleep\";\nimport type { ClientConfig } from \"./types\";\n\n/** Per-request options passed to {@link BaseHttpClient.request} and the convenience methods. */\nexport type ResponseType = \"auto\" | \"json\" | \"text\" | \"arrayBuffer\" | \"blob\";\n\nexport interface RequestOptions {\n\t/** HTTP method. Defaults to `\"GET\"`. */\n\tmethod?: HttpMethod;\n\t/**\n\t * Additional headers merged on top of `ClientConfig.defaultHeaders`.\n\t * These take precedence; `content-type: application/json` is always\n\t * present and is the lowest-priority default.\n\t */\n\theaders?: HeaderInput;\n\t/** Request body. Serialised to JSON by {@link fetchTransport}. Ignored for GET and HEAD. */\n\tbody?: unknown;\n\t/**\n\t * Query string parameters appended to the URL. `undefined` values are\n\t * omitted. Numbers and booleans are coerced to strings. Array values are\n\t * emitted as repeated query parameters.\n\t */\n\tquery?: QueryParams;\n\t/** Optional caller-provided abort signal. Composes with `timeoutMs`. */\n\tsignal?: AbortSignal;\n\t/**\n\t * Per-request timeout override in milliseconds. Takes precedence over\n\t * `ClientConfig.timeoutMs`. Throws {@link TimeoutError} when exceeded.\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Explicit cache key used by {@link createCachePlugin}. When omitted the\n\t * plugin derives a key from the method, URL, and query string.\n\t */\n\tcacheKey?: string;\n\t/**\n\t * Arbitrary string tags attached to the request context. Plugins may use\n\t * these for cache invalidation, metrics grouping, or filtering.\n\t */\n\ttags?: string[];\n\t/**\n\t * Controls how the response body is parsed. Defaults to content-type based\n\t * parsing: JSON responses become objects, everything else becomes text.\n\t */\n\tresponseType?: ResponseType;\n\t/**\n\t * Optional response parser for non-2xx bodies. Defaults to `\"auto\"` so APIs\n\t * that return binary success payloads can still surface text/JSON errors.\n\t */\n\terrorResponseType?: ResponseType;\n}\n\nexport interface ApiResponse<T = unknown> {\n\tdata: T;\n\tresponse: Response;\n\trequest: RequestContext;\n\tmeta: Record<string, unknown>;\n}\n\nconst DEFAULT_RETRIABLE_STATUS_CODES = [429, 500, 502, 503, 504];\n\n/**\n * Core HTTP client. Manages the plugin lifecycle, retry loop, and transport\n * dispatch for all requests.\n *\n * Plugins are initialised lazily on the first call to {@link request} (or any\n * convenience method). Call {@link dispose} when the client is no longer\n * needed so plugins can release timers, connections, or cache handles.\n *\n * Extend this class to add domain-specific methods while keeping the plugin\n * and transport infrastructure intact.\n *\n * @example\n * ```ts\n * // Prefer createClient() in application code:\n * const client = createClient({ baseUrl: \"https://api.example.com/v1\" });\n *\n * // Or subclass for wrapper packages:\n * class MyApiClient extends BaseHttpClient {\n * getUser(id: string) { return this.get<User>(`/users/${id}`); }\n * }\n * ```\n */\nexport class BaseHttpClient {\n\tprotected readonly config: ClientConfig;\n\tprotected readonly pluginManager: PluginManager;\n\tprivate initialized = false;\n\tprivate initPromise: Promise<void> | undefined;\n\n\tconstructor(config: ClientConfig) {\n\t\tthis.config = config;\n\t\tthis.pluginManager = new PluginManager(config.logger);\n\t\tfor (const plugin of config.plugins ?? []) {\n\t\t\tthis.pluginManager.register(plugin);\n\t\t}\n\t}\n\n\t/** Initializes all plugins. Called lazily on first request. */\n\tasync init(): Promise<void> {\n\t\tif (this.initialized) return;\n\t\tthis.initPromise ??= this.pluginManager\n\t\t\t.setup(this)\n\t\t\t.then(() => {\n\t\t\t\tthis.initialized = true;\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tthis.initPromise = undefined;\n\t\t\t\tthrow err;\n\t\t\t});\n\t\tawait this.initPromise;\n\t}\n\n\t/** Disposes all plugins. Call when the client is no longer needed. */\n\tasync dispose(): Promise<void> {\n\t\tawait this.pluginManager.dispose();\n\t\tthis.initialized = false;\n\t\tthis.initPromise = undefined;\n\t}\n\n\t/**\n\t * Executes an HTTP request through the full plugin pipeline.\n\t *\n\t * Lifecycle per attempt:\n\t * 1. Build `RequestContext` with merged headers, query, and retry state.\n\t * 2. Run `beforeRequest` hooks (ascending priority). A plugin may set\n\t * `ctx.syntheticResponse` to skip the transport entirely (e.g. cache hit).\n\t * 3. Merge any `retry.*` meta written by {@link createRetryPlugin}.\n\t * 4. Call transport (skipped when `syntheticResponse` is set).\n\t * 5. Parse the response body (JSON or text).\n\t * 6. Run `afterResponse` hooks (descending priority).\n\t * 7. Retry on retriable status codes; throw on terminal failures.\n\t *\n\t * @param path - Path appended to `ClientConfig.baseUrl`. Should start with `/`.\n\t * @param options - Per-request overrides for method, headers, body, query, etc.\n\t * @returns The parsed response body cast to `T`.\n\t * @throws {@link ApiError} for non-2xx responses.\n\t * @throws {@link RateLimitError} for 429 responses.\n\t * @throws {@link TimeoutError} when `timeoutMs` is exceeded.\n\t */\n\tasync request<T = unknown>(\n\t\tpath: string,\n\t\toptions: RequestOptions = {},\n\t): Promise<T> {\n\t\tconst result = await this.requestWithResponse<T>(path, options);\n\t\treturn result.data;\n\t}\n\n\t/**\n\t * Executes a request and returns the parsed body plus the final response\n\t * context. Use this in wrappers that need response headers, status, or\n\t * plugin metadata while keeping the same error/retry behaviour as\n\t * {@link request}.\n\t */\n\tasync requestWithResponse<T = unknown>(\n\t\tpath: string,\n\t\toptions: RequestOptions = {},\n\t): Promise<ApiResponse<T>> {\n\t\tawait this.init();\n\n\t\tconst transport =\n\t\t\tthis.config.transport ??\n\t\t\t(this.config.fetch\n\t\t\t\t? createFetchTransport(this.config.fetch)\n\t\t\t\t: fetchTransport);\n\t\tconst retryCfg = this.config.retry;\n\t\t// These are `let` so createRetryPlugin can override them per-request via\n\t\t// ctx.meta after beforeRequest runs (see merge block below).\n\t\tlet maxAttempts = normalizeRetryMaxAttempts(retryCfg?.maxAttempts);\n\t\tlet baseDelay = retryCfg?.delayMs ?? 500;\n\t\tlet jitter = retryCfg?.jitter ?? true;\n\t\tlet retriableCodes =\n\t\t\tretryCfg?.retriableStatusCodes ?? DEFAULT_RETRIABLE_STATUS_CODES;\n\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\t\tconst baseCtx: RequestContext = {\n\t\t\t\turl: resolveUrl(this.config.baseUrl, path),\n\t\t\t\tmethod: options.method ?? \"GET\",\n\t\t\t\theaders: mergeHeaders(\n\t\t\t\t\t{ \"content-type\": \"application/json\" },\n\t\t\t\t\tthis.config.defaultHeaders,\n\t\t\t\t\toptions.headers,\n\t\t\t\t),\n\t\t\t\tbody: options.body,\n\t\t\t\tquery: options.query,\n\t\t\t\tsignal: options.signal,\n\t\t\t\tmeta: {},\n\t\t\t\tcacheKey: options.cacheKey,\n\t\t\t\ttags: options.tags,\n\t\t\t\tretryCount: maxAttempts - 1 - attempt,\n\t\t\t\tattempt,\n\t\t\t\ttimeoutMs: options.timeoutMs ?? this.config.timeoutMs,\n\t\t\t};\n\n\t\t\tlet ctx: RequestContext;\n\t\t\ttry {\n\t\t\t\tctx = await this.pluginManager.beforeRequest(baseCtx);\n\t\t\t} catch (err) {\n\t\t\t\tawait this.pluginManager.onError(\n\t\t\t\t\terr,\n\t\t\t\t\tgetPluginErrorContext(err) ?? baseCtx,\n\t\t\t\t);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\t// Merge per-request retry overrides written by createRetryPlugin.\n\t\t\t// Only keys explicitly set by the plugin are present, so unset keys\n\t\t\t// leave the config-level defaults untouched.\n\t\t\t// Because the for-loop condition re-evaluates `attempt < maxAttempts`\n\t\t\t// on every iteration, updating maxAttempts here takes effect\n\t\t\t// immediately for all remaining attempts.\n\t\t\tif (ctx.meta[\"retry.maxAttempts\"] !== undefined) {\n\t\t\t\tmaxAttempts = normalizeRetryMaxAttempts(\n\t\t\t\t\tctx.meta[\"retry.maxAttempts\"] as number,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (ctx.meta[\"retry.delayMs\"] !== undefined)\n\t\t\t\tbaseDelay = ctx.meta[\"retry.delayMs\"] as number;\n\t\t\tif (ctx.meta[\"retry.jitter\"] !== undefined)\n\t\t\t\tjitter = ctx.meta[\"retry.jitter\"] as boolean;\n\t\t\tif (ctx.meta[\"retry.retriableStatusCodes\"] !== undefined)\n\t\t\t\tretriableCodes = ctx.meta[\"retry.retriableStatusCodes\"] as number[];\n\n\t\t\tconst retryCount = maxAttempts - 1 - attempt;\n\t\t\tif (ctx.retryCount !== retryCount) {\n\t\t\t\tctx = { ...ctx, retryCount };\n\t\t\t}\n\n\t\t\tlet rawResponse: Response;\n\t\t\tif (ctx.syntheticResponse) {\n\t\t\t\t// A plugin (e.g. cache) pre-populated the response — skip the\n\t\t\t\t// network entirely.\n\t\t\t\trawResponse = ctx.syntheticResponse;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\trawResponse = await transport.execute(ctx);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tawait this.pluginManager.onError(err, ctx);\n\t\t\t\t\tlastError = err;\n\n\t\t\t\t\tif (attempt < maxAttempts - 1) {\n\t\t\t\t\t\tawait this.waitForRetry(attempt, baseDelay, jitter);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst parsedBody = await parseBody(\n\t\t\t\trawResponse,\n\t\t\t\trawResponse.ok\n\t\t\t\t\t? options.responseType\n\t\t\t\t\t: (options.errorResponseType ?? \"auto\"),\n\t\t\t);\n\n\t\t\tlet resCtx: ResponseContext = {\n\t\t\t\trequest: ctx,\n\t\t\t\tresponse: rawResponse,\n\t\t\t\tparsedBody,\n\t\t\t\tmeta: {},\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tresCtx = await this.pluginManager.afterResponse(resCtx);\n\t\t\t} catch (err) {\n\t\t\t\tawait this.pluginManager.onError(err, ctx);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\tconst finalResponse = resCtx.response;\n\n\t\t\tif (!finalResponse.ok) {\n\t\t\t\tconst shouldRetry =\n\t\t\t\t\tretriableCodes.includes(finalResponse.status) &&\n\t\t\t\t\tattempt < maxAttempts - 1;\n\n\t\t\t\tif (shouldRetry) {\n\t\t\t\t\tif (finalResponse.status === 429) {\n\t\t\t\t\t\tconst wait = readRetryAfterMs(finalResponse);\n\t\t\t\t\t\tawait this.waitForRetry(attempt, wait ?? baseDelay, false);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.waitForRetry(attempt, baseDelay, jitter);\n\t\t\t\t\t}\n\t\t\t\t\tlastError = normalizeHttpError(finalResponse, resCtx.parsedBody);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst err = normalizeHttpError(finalResponse, resCtx.parsedBody);\n\t\t\t\tawait this.pluginManager.onError(err, ctx);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdata: resCtx.parsedBody as T,\n\t\t\t\tresponse: resCtx.response,\n\t\t\t\trequest: resCtx.request,\n\t\t\t\tmeta: resCtx.meta,\n\t\t\t};\n\t\t}\n\n\t\tthrow lastError;\n\t}\n\n\t// ─── Convenience methods ────────────────────────────────────────────────────\n\t// Each method is a thin wrapper around request() that fixes the HTTP verb.\n\n\t/** Sends a GET request. The response body is not cached unless a cache plugin is registered. */\n\tget<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"GET\" });\n\t}\n\n\tpost<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: Omit<RequestOptions, \"method\" | \"body\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"POST\", body });\n\t}\n\n\tput<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: Omit<RequestOptions, \"method\" | \"body\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"PUT\", body });\n\t}\n\n\tpatch<T = unknown>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\toptions?: Omit<RequestOptions, \"method\" | \"body\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"PATCH\", body });\n\t}\n\n\tdelete<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"DELETE\" });\n\t}\n\n\thead<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"HEAD\" });\n\t}\n\n\toptions<T = unknown>(\n\t\tpath: string,\n\t\toptions?: Omit<RequestOptions, \"method\">,\n\t): Promise<T> {\n\t\treturn this.request<T>(path, { ...options, method: \"OPTIONS\" });\n\t}\n\n\t/**\n\t * Executes a GraphQL query or mutation against a single endpoint path.\n\t *\n\t * The request is a `POST` with `content-type: application/json` carrying\n\t * `{ query, variables?, operationName? }` as the body. It flows through\n\t * the full plugin lifecycle (beforeRequest → transport → afterResponse →\n\t * onError) and respects all retry configuration, exactly like REST calls.\n\t *\n\t * **Error handling:**\n\t * - HTTP-level failures (429, 500, timeout) throw the same error classes\n\t * as REST requests (`RateLimitError`, `ApiError`, `TimeoutError`).\n\t * - A successful HTTP 200 that contains a non-empty `errors` array throws\n\t * {@link GraphQLRequestError}, which extends `ApiError`.\n\t *\n\t * **Caching:**\n\t * The cache plugin skips `POST` requests by default. Pass an explicit\n\t * `cacheKey` in options to opt a specific operation into caching.\n\t *\n\t * @typeParam TData - Shape of the `data` field in the GraphQL response.\n\t * @typeParam TVariables - Shape of the `variables` object. Defaults to\n\t * `Record<string, unknown>`.\n\t * @param path - Endpoint path, e.g. `\"/graphql\"`. Appended to `baseUrl`.\n\t * @param options - Query document, variables, and optional per-request overrides.\n\t * @returns The `data` field from the GraphQL response envelope.\n\t * @throws {@link GraphQLRequestError} when `response.errors` is non-empty.\n\t * @throws {@link ApiError} / {@link RateLimitError} / {@link TimeoutError} on\n\t * HTTP-level failures.\n\t *\n\t * @example\n\t * ```ts\n\t * const data = await client.graphql<GetUserQuery, GetUserQueryVariables>(\n\t * \"/graphql\",\n\t * { query: GET_USER, variables: { id: \"123\" } },\n\t * );\n\t * ```\n\t */\n\tasync graphql<\n\t\tTData = unknown,\n\t\tTVariables extends object = Record<string, unknown>,\n\t>(path: string, options: GraphQLRequestOptions<TVariables>): Promise<TData> {\n\t\tconst {\n\t\t\tquery,\n\t\t\tvariables,\n\t\t\toperationName,\n\t\t\theaders,\n\t\t\tsignal,\n\t\t\ttimeoutMs,\n\t\t\tcacheKey,\n\t\t\ttags,\n\t\t} = options;\n\n\t\tconst envelope = await this.request<GraphQLResponse<TData>>(path, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tquery,\n\t\t\t\t...(variables !== undefined && { variables }),\n\t\t\t\t...(operationName !== undefined && { operationName }),\n\t\t\t},\n\t\t\theaders: mergeHeaders(headers, { \"content-type\": \"application/json\" }),\n\t\t\tsignal,\n\t\t\ttimeoutMs,\n\t\t\tcacheKey,\n\t\t\ttags,\n\t\t});\n\n\t\t// Surface GraphQL application-layer errors as a typed exception.\n\t\t// We throw even when partial data is present — callers who need\n\t\t// partial results can catch GraphQLRequestError and read .partialData.\n\t\tif (envelope.errors && envelope.errors.length > 0) {\n\t\t\tthrow new GraphQLRequestError(envelope.errors, envelope.data);\n\t\t}\n\n\t\t// data may be undefined if the server returned an empty response —\n\t\t// safe to cast because TData defaults to unknown.\n\t\treturn envelope.data as TData;\n\t}\n\n\tprivate async waitForRetry(\n\t\tattempt: number,\n\t\tbaseDelay: number,\n\t\tuseJitter: boolean,\n\t): Promise<void> {\n\t\t// Exponential backoff: delay * 2^attempt\n\t\tconst exponential = baseDelay * 2 ** attempt;\n\t\tconst ms = useJitter\n\t\t\t? exponential * (0.5 + Math.random() * 0.5)\n\t\t\t: exponential;\n\t\tawait sleep(Math.round(ms));\n\t}\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function parseBody(\n\tresponse: Response,\n\tresponseType: ResponseType = \"auto\",\n): Promise<unknown> {\n\tif (responseType === \"arrayBuffer\") return response.arrayBuffer();\n\tif (responseType === \"blob\") return response.blob();\n\n\tif (response.status === 204 || response.status === 205) return undefined;\n\tif (response.headers.get(\"content-length\") === \"0\") return undefined;\n\n\tconst text = await response.text();\n\tif (responseType === \"text\") return text;\n\tif (!text) return undefined;\n\n\tif (responseType === \"json\") return JSON.parse(text);\n\n\tif (isJsonContentType(response.headers.get(\"content-type\"))) {\n\t\treturn JSON.parse(text);\n\t}\n\treturn text;\n}\n\nfunction normalizeHttpError(response: Response, body: unknown): ApiError {\n\tif (response.status === 429) {\n\t\treturn new RateLimitError(readRetryAfterMs(response), body);\n\t}\n\treturn new ApiError(\n\t\t`Request failed with status ${response.status}`,\n\t\tresponse.status,\n\t\tbody,\n\t);\n}\n\nfunction readRetryAfterMs(response: Response): number | undefined {\n\tconst raw = response.headers.get(\"retry-after\");\n\tif (!raw) return undefined;\n\n\tconst seconds = Number(raw);\n\tif (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);\n\n\tconst date = Date.parse(raw);\n\tif (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n\n\treturn undefined;\n}\n","import { BaseHttpClient } from \"./BaseHttpClient\";\nimport type { ClientConfig } from \"./types\";\n\n/**\n * Factory function that creates a {@link BaseHttpClient} from the given\n * config. Prefer this over `new BaseHttpClient(config)` in application code\n * so that the concrete class stays an implementation detail.\n *\n * @example\n * ```ts\n * const client = createClient({\n * baseUrl: \"https://api.example.com/v1\",\n * defaultHeaders: { \"x-api-key\": \"secret\" },\n * retry: { maxAttempts: 3, delayMs: 300 },\n * plugins: [createLoggerPlugin(), createCachePlugin({ ttlMs: 60_000 })],\n * });\n * ```\n */\nexport function createClient(config: ClientConfig): BaseHttpClient {\n\treturn new BaseHttpClient(config);\n}\n","import { GraphQLRequestError } from \"../graphql/GraphQLRequestError\";\nimport { ApiError } from \"./ApiError\";\nimport { RateLimitError } from \"./RateLimitError\";\nimport { TimeoutError } from \"./TimeoutError\";\n\nexport type ApiCoreError =\n\t| ApiError\n\t| RateLimitError\n\t| TimeoutError\n\t| GraphQLRequestError;\n\nexport function isApiError(error: unknown): error is ApiError {\n\treturn error instanceof ApiError;\n}\n\nexport function isRateLimitError(error: unknown): error is RateLimitError {\n\treturn error instanceof RateLimitError;\n}\n\nexport function isTimeoutError(error: unknown): error is TimeoutError {\n\treturn error instanceof TimeoutError;\n}\n\nexport function isGraphQLRequestError(\n\terror: unknown,\n): error is GraphQLRequestError {\n\treturn error instanceof GraphQLRequestError;\n}\n\nexport function isApiCoreError(error: unknown): error is ApiCoreError {\n\treturn isApiError(error) || isTimeoutError(error);\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { MaybePromise } from \"../../types/common\";\nimport type { AuthPluginOptions } from \"./types\";\n\ntype TokenInput =\n\t| string\n\t| (() => MaybePromise<string | null | undefined>)\n\t| AuthPluginOptions;\n\n/**\n * Adds an auth token header before each request. The token can be static or\n * loaded asynchronously per request, which covers wrappers with refreshable\n * access tokens.\n */\nexport function createAuthPlugin(input: TokenInput): ApiPlugin {\n\tconst options = normalizeOptions(input);\n\tconst headerName = (options.headerName ?? \"authorization\").toLowerCase();\n\tconst scheme = options.scheme === undefined ? \"Bearer\" : options.scheme;\n\n\treturn {\n\t\tname: \"auth\",\n\t\tpriority: 2,\n\n\t\tasync beforeRequest(ctx) {\n\t\t\tconst token = await options.getToken();\n\t\t\tif (!token) return ctx;\n\n\t\t\treturn {\n\t\t\t\t...ctx,\n\t\t\t\theaders: {\n\t\t\t\t\t...ctx.headers,\n\t\t\t\t\t[headerName]: scheme ? `${scheme} ${token}` : token,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction normalizeOptions(input: TokenInput): AuthPluginOptions {\n\tif (typeof input === \"string\") {\n\t\treturn { getToken: () => input };\n\t}\n\tif (typeof input === \"function\") {\n\t\treturn { getToken: input };\n\t}\n\treturn input;\n}\n","import type { CacheStore } from \"./types\";\n\ninterface CacheEntry {\n\tvalue: unknown;\n\texpiresAt: number | null;\n}\n\nexport class MemoryStore implements CacheStore {\n\tprivate readonly store = new Map<string, CacheEntry>();\n\n\tget(key: string): unknown | undefined {\n\t\tconst entry = this.store.get(key);\n\t\tif (!entry) return undefined;\n\t\tif (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n\t\t\tthis.store.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry.value;\n\t}\n\n\tset(key: string, value: unknown, ttlMs?: number): void {\n\t\tthis.store.set(key, {\n\t\t\tvalue,\n\t\t\texpiresAt: ttlMs != null ? Date.now() + ttlMs : null,\n\t\t});\n\t}\n\n\tdelete(key: string): void {\n\t\tthis.store.delete(key);\n\t}\n\n\tclear(): void {\n\t\tthis.store.clear();\n\t}\n}\n","import type { RequestContext } from \"../../context/RequestContext\";\nimport { buildUrl } from \"../../utils/buildUrl\";\nimport { MemoryStore } from \"./memoryStore\";\nimport type { CachePlugin, CachePluginOptions } from \"./types\";\n\nconst DEFAULT_CACHEABLE_METHODS = [\"GET\"] as const;\nconst CACHE_HIT_META_KEY = \"cache.hit\";\n\nexport function createCachePlugin(\n\toptions: CachePluginOptions = {},\n): CachePlugin {\n\tconst store = options.store ?? new MemoryStore();\n\tconst ttlMs = options.ttlMs;\n\tconst methods: string[] = options.methods ?? [...DEFAULT_CACHEABLE_METHODS];\n\tconst generateKey = options.generateKey ?? defaultCacheKey;\n\n\t// tag → Set<cacheKey>: populated during afterResponse, used by invalidateByTag.\n\tconst tagIndex = new Map<string, Set<string>>();\n\n\treturn {\n\t\tname: \"cache\",\n\t\tpriority: 20,\n\n\t\tasync beforeRequest(ctx) {\n\t\t\tif (!methods.includes(ctx.method)) return ctx;\n\n\t\t\tconst key = ctx.cacheKey ?? generateKey(ctx);\n\t\t\tconst cached = await store.get(key);\n\n\t\t\tif (cached !== undefined) {\n\t\t\t\t// Build a synthetic Response so the rest of the pipeline\n\t\t\t\t// (afterResponse, status checks) sees a uniform shape.\n\t\t\t\tconst syntheticResponse = new Response(serializeCachedBody(cached), {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t});\n\n\t\t\t\t// Setting syntheticResponse tells BaseHttpClient to skip the\n\t\t\t\t// transport entirely and use this response directly.\n\t\t\t\treturn {\n\t\t\t\t\t...ctx,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\t...ctx.meta,\n\t\t\t\t\t\t[CACHE_HIT_META_KEY]: { key, data: cached },\n\t\t\t\t\t},\n\t\t\t\t\tsyntheticResponse,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...ctx,\n\t\t\t\tmeta: { ...ctx.meta, \"cache.key\": key },\n\t\t\t};\n\t\t},\n\n\t\tasync afterResponse(ctx) {\n\t\t\tconst hit = ctx.request.meta[CACHE_HIT_META_KEY] as\n\t\t\t\t| { key: string; data: unknown }\n\t\t\t\t| undefined;\n\n\t\t\tif (hit) {\n\t\t\t\treturn {\n\t\t\t\t\t...ctx,\n\t\t\t\t\tparsedBody: hit.data,\n\t\t\t\t\tmeta: { ...ctx.meta, \"cache.served\": true },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst key = ctx.request.meta[\"cache.key\"] as string | undefined;\n\t\t\tif (key && methods.includes(ctx.request.method) && ctx.response.ok) {\n\t\t\t\tawait store.set(key, ctx.parsedBody, ttlMs);\n\t\t\t\tctx.meta[\"cache.stored\"] = true;\n\n\t\t\t\t// Record tag → key associations for invalidateByTag.\n\t\t\t\tfor (const tag of ctx.request.tags ?? []) {\n\t\t\t\t\tif (!tagIndex.has(tag)) tagIndex.set(tag, new Set());\n\t\t\t\t\ttagIndex.get(tag)?.add(key);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn ctx;\n\t\t},\n\n\t\tasync invalidate(key: string): Promise<void> {\n\t\t\tawait store.delete(key);\n\t\t\t// Clean up any tag index entries pointing to this key.\n\t\t\tfor (const keys of tagIndex.values()) {\n\t\t\t\tkeys.delete(key);\n\t\t\t}\n\t\t},\n\n\t\tasync invalidateByTag(tag: string): Promise<void> {\n\t\t\tconst keys = tagIndex.get(tag);\n\t\t\tif (!keys || keys.size === 0) return;\n\t\t\tfor (const key of keys) {\n\t\t\t\tawait store.delete(key);\n\t\t\t\t// Remove this key from all other tag index entries too.\n\t\t\t\tfor (const otherKeys of tagIndex.values()) {\n\t\t\t\t\totherKeys.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttagIndex.delete(tag);\n\t\t},\n\t};\n}\n\nfunction defaultCacheKey(ctx: RequestContext): string {\n\treturn `${ctx.method}:${buildUrl(ctx.url, ctx.query)}`;\n}\n\nfunction serializeCachedBody(value: unknown): BodyInit | null {\n\ttry {\n\t\treturn JSON.stringify(value) ?? null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { LoggerPluginOptions } from \"./types\";\n\n/**\n * Creates a plugin that logs request start, response status, and errors.\n *\n * Log lines are prefixed with `[api-core]` and include the HTTP method, URL,\n * attempt number (on `beforeRequest`), and status code (on `afterResponse`).\n *\n * Priority `10` means it runs _after_ auth or header-mutation plugins\n * (priority < 10) so the logged URL and headers reflect the final request,\n * but _before_ the cache plugin (priority `20`) so cache hits are still\n * visible in the log.\n *\n * @example\n * ```ts\n * createClient({\n * baseUrl: \"https://api.example.com\",\n * plugins: [createLoggerPlugin({ logRequest: true, logResponse: true })],\n * });\n * ```\n */\nexport function createLoggerPlugin(\n\toptions: LoggerPluginOptions = {},\n): ApiPlugin {\n\tconst {\n\t\tlogRequest = true,\n\t\tlogResponse = true,\n\t\tlogError = true,\n\t\tlogger = console,\n\t} = options;\n\n\treturn {\n\t\tname: \"logger\",\n\t\tpriority: 10,\n\n\t\tbeforeRequest(ctx) {\n\t\t\tif (logRequest) {\n\t\t\t\tlogger.info(`[api-core] --> ${ctx.method} ${ctx.url}`, {\n\t\t\t\t\tattempt: ctx.attempt,\n\t\t\t\t\tbody: ctx.body,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn ctx;\n\t\t},\n\n\t\tafterResponse(ctx) {\n\t\t\tif (logResponse) {\n\t\t\t\tlogger.info(\n\t\t\t\t\t`[api-core] <-- ${ctx.response.status} ${ctx.request.method} ${ctx.request.url}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn ctx;\n\t\t},\n\n\t\tonError(error, ctx) {\n\t\t\tif (logError) {\n\t\t\t\tlogger.error(`[api-core] ERR ${ctx.method} ${ctx.url}`, error);\n\t\t\t}\n\t\t},\n\t};\n}\n","import type { RequestContext } from \"../../context/RequestContext\";\nimport type { ApiPlugin } from \"../../plugin/types\";\nimport type { RateLimitPluginOptions } from \"./types\";\n\nconst RELEASE_META_KEY = \"rateLimit.release\";\n\ninterface QueueItem {\n\tresolve: (release: () => void) => void;\n}\n\n/**\n * Throttles request starts before they reach the transport. Supports\n * concurrency, minimum spacing, and fixed-window request budgets.\n */\nexport function createRateLimitPlugin(\n\toptions: RateLimitPluginOptions = {},\n): ApiPlugin {\n\tconst maxConcurrent = options.maxConcurrent ?? Number.POSITIVE_INFINITY;\n\tconst minTimeMs = options.minTimeMs ?? 0;\n\tconst maxRequestsPerInterval = options.maxRequestsPerInterval;\n\tconst intervalMs = options.intervalMs;\n\n\tif (maxConcurrent <= 0) {\n\t\tthrow new Error(\"maxConcurrent must be greater than 0\");\n\t}\n\tif (minTimeMs < 0) {\n\t\tthrow new Error(\"minTimeMs must be greater than or equal to 0\");\n\t}\n\tif (\n\t\t(maxRequestsPerInterval !== undefined || intervalMs !== undefined) &&\n\t\t(!maxRequestsPerInterval ||\n\t\t\tmaxRequestsPerInterval <= 0 ||\n\t\t\t!intervalMs ||\n\t\t\tintervalMs <= 0)\n\t) {\n\t\tthrow new Error(\n\t\t\t\"maxRequestsPerInterval and intervalMs must both be greater than 0\",\n\t\t);\n\t}\n\n\tconst queue: QueueItem[] = [];\n\tconst starts: number[] = [];\n\tlet active = 0;\n\tlet lastStartAt = 0;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\n\tconst processQueue = () => {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\n\t\twhile (queue.length > 0) {\n\t\t\tconst now = Date.now();\n\t\t\tpruneStarts(now);\n\n\t\t\tif (active >= maxConcurrent) return;\n\n\t\t\tconst waitMs = getWaitMs(now);\n\t\t\tif (waitMs > 0) {\n\t\t\t\ttimer = setTimeout(processQueue, waitMs);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst item = queue.shift();\n\t\t\tif (!item) return;\n\n\t\t\tactive++;\n\t\t\tlastStartAt = now;\n\t\t\tstarts.push(now);\n\n\t\t\tlet released = false;\n\t\t\titem.resolve(() => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tactive--;\n\t\t\t\tprocessQueue();\n\t\t\t});\n\t\t}\n\t};\n\n\tconst acquire = () =>\n\t\tnew Promise<() => void>((resolve) => {\n\t\t\tqueue.push({ resolve });\n\t\t\tprocessQueue();\n\t\t});\n\n\tconst release = (ctx: RequestContext) => {\n\t\tconst releaseFn = ctx.meta[RELEASE_META_KEY] as (() => void) | undefined;\n\t\tif (!releaseFn) return;\n\t\tdelete ctx.meta[RELEASE_META_KEY];\n\t\treleaseFn();\n\t};\n\n\tconst pruneStarts = (now: number) => {\n\t\tif (!intervalMs) return;\n\t\twhile (starts.length > 0 && now - (starts[0] ?? 0) >= intervalMs) {\n\t\t\tstarts.shift();\n\t\t}\n\t};\n\n\tconst getWaitMs = (now: number): number => {\n\t\tconst spacingWait = Math.max(0, lastStartAt + minTimeMs - now);\n\t\tif (!maxRequestsPerInterval || !intervalMs) return spacingWait;\n\n\t\tif (starts.length < maxRequestsPerInterval) return spacingWait;\n\n\t\tconst oldest = starts[0] ?? now;\n\t\tconst intervalWait = Math.max(0, oldest + intervalMs - now);\n\t\treturn Math.max(spacingWait, intervalWait);\n\t};\n\n\treturn {\n\t\tname: \"rate-limit\",\n\t\tpriority: 1,\n\n\t\tasync beforeRequest(ctx) {\n\t\t\tconst releaseFn = await acquire();\n\t\t\treturn {\n\t\t\t\t...ctx,\n\t\t\t\tmeta: { ...ctx.meta, [RELEASE_META_KEY]: releaseFn },\n\t\t\t};\n\t\t},\n\n\t\tafterResponse(ctx) {\n\t\t\trelease(ctx.request);\n\t\t\treturn ctx;\n\t\t},\n\n\t\tonError(_error, ctx) {\n\t\t\trelease(ctx);\n\t\t},\n\t};\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport { normalizeRetryMaxAttempts } from \"../../utils/normalizeRetryMaxAttempts\";\nimport type { RetryPluginOptions } from \"./types\";\n\n/**\n * Writes retry configuration into request context meta so the\n * BaseHttpClient retry loop can read it. Use this when you need\n * per-request retry overrides rather than global ClientConfig.retry.\n */\nexport function createRetryPlugin(options: RetryPluginOptions = {}): ApiPlugin {\n\treturn {\n\t\tname: \"retry\",\n\t\tpriority: 5,\n\n\t\tbeforeRequest(ctx) {\n\t\t\tconst meta = { ...ctx.meta };\n\t\t\tlet retryCount = ctx.retryCount;\n\n\t\t\t// Only write keys for values the caller explicitly provided.\n\t\t\t// Unset options fall through to ClientConfig.retry defaults inside\n\t\t\t// BaseHttpClient, so the plugin does not need to supply fallbacks.\n\t\t\tif (options.maxAttempts !== undefined) {\n\t\t\t\tmeta[\"retry.maxAttempts\"] = options.maxAttempts;\n\t\t\t\tretryCount =\n\t\t\t\t\tnormalizeRetryMaxAttempts(options.maxAttempts) - 1 - ctx.attempt;\n\t\t\t}\n\t\t\tif (options.delayMs !== undefined)\n\t\t\t\tmeta[\"retry.delayMs\"] = options.delayMs;\n\t\t\tif (options.jitter !== undefined) meta[\"retry.jitter\"] = options.jitter;\n\t\t\tif (options.retriableStatusCodes !== undefined)\n\t\t\t\tmeta[\"retry.retriableStatusCodes\"] = options.retriableStatusCodes;\n\t\t\treturn { ...ctx, meta, retryCount };\n\t\t},\n\t};\n}\n","import type { ApiPlugin } from \"../../plugin/types\";\nimport type { TimeoutPluginOptions } from \"./types\";\n\n/**\n * Sets `ctx.timeoutMs` on every request so all requests made by this client\n * abort after the configured duration. The actual abort and\n * {@link TimeoutError} are handled by {@link fetchTransport}.\n *\n * Priority `1` ensures the timeout is stamped before any other plugin (e.g.\n * logger, cache) runs — plugins that read `ctx.timeoutMs` will always see it.\n * Use a `beforeRequest` hook with a lower priority to override per-request.\n *\n * Prefer `ClientConfig.timeoutMs` for a static global timeout. Use this\n * plugin when you need to set or change the timeout through the plugin\n * pipeline (e.g. from environment config loaded asynchronously in `setup`).\n *\n * @example\n * ```ts\n * createClient({\n * baseUrl: \"https://api.example.com\",\n * plugins: [createTimeoutPlugin({ timeoutMs: 5_000 })],\n * });\n * ```\n */\nexport function createTimeoutPlugin(options: TimeoutPluginOptions): ApiPlugin {\n\treturn {\n\t\tname: \"timeout\",\n\t\tpriority: 1,\n\n\t\tbeforeRequest(ctx) {\n\t\t\treturn { ...ctx, timeoutMs: options.timeoutMs };\n\t\t},\n\t};\n}\n","/**\n * Lightweight GraphQL template tag.\n *\n * This intentionally does not parse into a DocumentNode. It preserves the\n * query string while still giving GraphQL-aware tooling a familiar `gql` tag.\n */\nexport function gql(\n\tchunks: TemplateStringsArray,\n\t...values: unknown[]\n): string {\n\tconst source = chunks.reduce(\n\t\t(source, chunk, index) =>\n\t\t\t`${source}${chunk}${index in values ? String(values[index]) : \"\"}`,\n\t\t\"\",\n\t);\n\n\treturn dedupeFragmentDefinitions(source);\n}\n\nfunction dedupeFragmentDefinitions(source: string): string {\n\tconst seen = new Set<string>();\n\tconst fragmentPattern =\n\t\t/\\bfragment\\s+([_A-Za-z][_0-9A-Za-z]*)\\s+on\\s+[_A-Za-z][_0-9A-Za-z]*/g;\n\n\tlet result = \"\";\n\tlet cursor = 0;\n\tlet match = fragmentPattern.exec(source);\n\n\twhile (match) {\n\t\tconst name = match[1];\n\t\tif (!name) {\n\t\t\tmatch = fragmentPattern.exec(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst bodyStart = source.indexOf(\"{\", fragmentPattern.lastIndex);\n\t\tif (bodyStart === -1) {\n\t\t\tmatch = fragmentPattern.exec(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst bodyEnd = findMatchingBrace(source, bodyStart);\n\t\tif (bodyEnd === -1) {\n\t\t\tmatch = fragmentPattern.exec(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst fragmentStart = match.index;\n\t\tconst fragmentEnd = consumeTrailingWhitespace(source, bodyEnd + 1);\n\n\t\tif (!seen.has(name)) {\n\t\t\tseen.add(name);\n\t\t\tresult += source.slice(cursor, fragmentEnd);\n\t\t} else {\n\t\t\tresult += source.slice(cursor, fragmentStart);\n\t\t}\n\n\t\tcursor = fragmentEnd;\n\t\tfragmentPattern.lastIndex = fragmentEnd;\n\t\tmatch = fragmentPattern.exec(source);\n\t}\n\n\treturn result + source.slice(cursor);\n}\n\nfunction findMatchingBrace(source: string, openBraceIndex: number): number {\n\tlet depth = 0;\n\n\tfor (let index = openBraceIndex; index < source.length; index++) {\n\t\tconst char = source[index];\n\t\tif (char === \"{\") depth++;\n\t\tif (char === \"}\") depth--;\n\t\tif (depth === 0) return index;\n\t}\n\n\treturn -1;\n}\n\nfunction consumeTrailingWhitespace(source: string, index: number): number {\n\tlet next = index;\n\twhile (next < source.length && /\\s/.test(source[next] ?? \"\")) {\n\t\tnext++;\n\t}\n\treturn next;\n}\n"],"mappings":"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"}
|
|
@@ -16,22 +16,22 @@ GraphQL application errors.
|
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
isApiError,
|
|
20
|
+
isGraphQLRequestError,
|
|
21
|
+
isRateLimitError,
|
|
22
|
+
isTimeoutError,
|
|
23
23
|
} from "@api-wrappers/api-core";
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
26
|
await client.get("/resource");
|
|
27
27
|
} catch (error) {
|
|
28
|
-
if (error
|
|
28
|
+
if (isRateLimitError(error)) {
|
|
29
29
|
console.log("retry after", error.retryAfterMs);
|
|
30
|
-
} else if (error
|
|
30
|
+
} else if (isTimeoutError(error)) {
|
|
31
31
|
console.log("request timed out");
|
|
32
|
-
} else if (error
|
|
32
|
+
} else if (isGraphQLRequestError(error)) {
|
|
33
33
|
console.log(error.graphqlErrors);
|
|
34
|
-
} else if (error
|
|
34
|
+
} else if (isApiError(error)) {
|
|
35
35
|
console.log(error.status, error.responseBody);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -46,7 +46,7 @@ on `error.responseBody`.
|
|
|
46
46
|
try {
|
|
47
47
|
await client.get("/missing");
|
|
48
48
|
} catch (error) {
|
|
49
|
-
if (error
|
|
49
|
+
if (isApiError(error)) {
|
|
50
50
|
console.log(error.status);
|
|
51
51
|
console.log(error.responseBody);
|
|
52
52
|
}
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
`RateLimitError.retryAfterMs` is set when the server provides `retry-after`.
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
|
-
if (error
|
|
61
|
+
if (isRateLimitError(error) && error.retryAfterMs) {
|
|
62
62
|
await sleep(error.retryAfterMs);
|
|
63
63
|
}
|
|
64
64
|
```
|
|
@@ -52,6 +52,10 @@ await client.get("/movies", {
|
|
|
52
52
|
| `responseType` | Override response parsing with `json`, `text`, `arrayBuffer`, or `blob`. Defaults to `auto`. |
|
|
53
53
|
| `errorResponseType` | Override non-2xx body parsing. Defaults to `auto`. |
|
|
54
54
|
|
|
55
|
+
`headers` and `defaultHeaders` accept any `HeadersInit` shape: plain objects,
|
|
56
|
+
native `Headers`, or `[name, value]` tuples. Header names are normalized
|
|
57
|
+
case-insensitively before the request reaches plugins or transports.
|
|
58
|
+
|
|
55
59
|
## Response Metadata
|
|
56
60
|
|
|
57
61
|
Use `requestWithResponse` when the wrapper needs headers, status, request data,
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
```ts
|
|
6
6
|
interface ClientConfig {
|
|
7
7
|
baseUrl: string;
|
|
8
|
-
defaultHeaders?:
|
|
8
|
+
defaultHeaders?: HeaderInput;
|
|
9
9
|
plugins?: ApiPlugin[];
|
|
10
10
|
transport?: Transport;
|
|
11
|
-
fetch?:
|
|
11
|
+
fetch?: FetchLike;
|
|
12
12
|
timeoutMs?: number;
|
|
13
13
|
retry?: RetryConfig;
|
|
14
14
|
logger?: LoggerInterface;
|
|
@@ -31,7 +31,7 @@ interface ClientConfig {
|
|
|
31
31
|
```ts
|
|
32
32
|
interface RequestOptions {
|
|
33
33
|
method?: HttpMethod;
|
|
34
|
-
headers?:
|
|
34
|
+
headers?: HeaderInput;
|
|
35
35
|
body?: unknown;
|
|
36
36
|
query?: QueryParams;
|
|
37
37
|
signal?: AbortSignal;
|
|
@@ -48,7 +48,7 @@ interface GraphQLRequestOptions<TVariables extends object = Record<string, unkno
|
|
|
48
48
|
query: string;
|
|
49
49
|
variables?: TVariables;
|
|
50
50
|
operationName?: string;
|
|
51
|
-
headers?:
|
|
51
|
+
headers?: HeaderInput;
|
|
52
52
|
signal?: AbortSignal;
|
|
53
53
|
timeoutMs?: number;
|
|
54
54
|
cacheKey?: string;
|
|
@@ -72,6 +72,13 @@ interface RetryConfig {
|
|
|
72
72
|
## Query Types
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
+
type HeaderInput = HeadersInit;
|
|
76
|
+
|
|
77
|
+
type FetchLike = (
|
|
78
|
+
input: string | URL | Request,
|
|
79
|
+
init?: RequestInit,
|
|
80
|
+
) => Promise<Response>;
|
|
81
|
+
|
|
75
82
|
type QueryPrimitive = string | number | boolean;
|
|
76
83
|
|
|
77
84
|
type QueryValue =
|
|
@@ -86,6 +93,10 @@ type QueryParams = Record<string, QueryValue>;
|
|
|
86
93
|
Nullish query values are skipped. Array query values are emitted as repeated
|
|
87
94
|
query parameters.
|
|
88
95
|
|
|
96
|
+
Headers accept the same shapes as `fetch`: a plain object, a `Headers`
|
|
97
|
+
instance, or `[name, value]` tuples. Header names are normalized to lowercase
|
|
98
|
+
internally and later sources override earlier sources case-insensitively.
|
|
99
|
+
|
|
89
100
|
## `Transport`
|
|
90
101
|
|
|
91
102
|
```ts
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
- `RateLimitError`
|
|
22
22
|
- `TimeoutError`
|
|
23
23
|
- `GraphQLRequestError`
|
|
24
|
+
- `ApiCoreError`
|
|
25
|
+
- `isApiCoreError`
|
|
26
|
+
- `isApiError`
|
|
27
|
+
- `isGraphQLRequestError`
|
|
28
|
+
- `isRateLimitError`
|
|
29
|
+
- `isTimeoutError`
|
|
24
30
|
|
|
25
31
|
## Plugin System
|
|
26
32
|
|
|
@@ -55,12 +61,14 @@
|
|
|
55
61
|
## Transport
|
|
56
62
|
|
|
57
63
|
- `Transport`
|
|
64
|
+
- `FetchLike`
|
|
58
65
|
- `createFetchTransport`
|
|
59
66
|
- `fetchTransport`
|
|
60
67
|
|
|
61
68
|
## Shared Types
|
|
62
69
|
|
|
63
70
|
- `HttpMethod`
|
|
71
|
+
- `HeaderInput`
|
|
64
72
|
- `MaybePromise`
|
|
65
73
|
- `QueryParams`
|
|
66
74
|
- `QueryPrimitive`
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api-wrappers/api-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Shared HTTP client runtime for the api-wrappers organisation. Provides request orchestration, a plugin lifecycle, transport abstraction, and built-in cache/retry/logger plugins.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/Api-Wrappers/api-core"
|
|
8
8
|
},
|
|
9
|
+
"homepage": "https://github.com/Api-Wrappers/api-core#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/Api-Wrappers/api-core/issues"
|
|
12
|
+
},
|
|
9
13
|
"type": "module",
|
|
14
|
+
"packageManager": "bun@1.3.13",
|
|
10
15
|
"main": "./dist/index.cjs",
|
|
11
16
|
"module": "./dist/index.mjs",
|
|
12
17
|
"types": "./dist/index.d.mts",
|
|
@@ -20,11 +25,14 @@
|
|
|
20
25
|
"types": "./dist/index.d.cts",
|
|
21
26
|
"default": "./dist/index.cjs"
|
|
22
27
|
}
|
|
23
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
24
30
|
},
|
|
31
|
+
"sideEffects": false,
|
|
25
32
|
"files": [
|
|
26
33
|
"dist",
|
|
27
|
-
"docs"
|
|
34
|
+
"docs",
|
|
35
|
+
"CHANGELOG.md"
|
|
28
36
|
],
|
|
29
37
|
"keywords": [
|
|
30
38
|
"http",
|
|
@@ -41,8 +49,15 @@
|
|
|
41
49
|
"scripts": {
|
|
42
50
|
"build": "tsdown",
|
|
43
51
|
"test": "bun test",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
44
53
|
"check": "biome check --write .",
|
|
45
|
-
"check:ci": "biome check ."
|
|
54
|
+
"check:ci": "biome check .",
|
|
55
|
+
"pack:dry-run": "bun pm pack --dry-run",
|
|
56
|
+
"verify": "bun run check:ci && bun run typecheck && bun test && bun run build && bun run pack:dry-run",
|
|
57
|
+
"prepack": "bun run build"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18"
|
|
46
61
|
},
|
|
47
62
|
"publishConfig": {
|
|
48
63
|
"access": "public"
|
|
@@ -50,7 +65,8 @@
|
|
|
50
65
|
"devDependencies": {
|
|
51
66
|
"@biomejs/biome": "2.4.9",
|
|
52
67
|
"@types/bun": "1.3.11",
|
|
53
|
-
"tsdown": "0.21.7"
|
|
68
|
+
"tsdown": "0.21.7",
|
|
69
|
+
"typescript": "^5.9.3"
|
|
54
70
|
},
|
|
55
71
|
"peerDependencies": {
|
|
56
72
|
"typescript": "^5"
|