@routar/core 1.6.0 → 1.7.0
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/dist/index.cjs +273 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +252 -24
- package/dist/index.d.ts +252 -24
- package/dist/index.js +272 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/define-router.ts","../src/utils/path.ts","../src/utils/validate.ts","../src/create-api.ts","../src/create-executor.ts","../src/middleware.ts","../src/utils/params.ts","../src/create-fetch-executor.ts","../src/define-endpoint.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { RouterDef, RouterEndpoints } from \"./types.js\";\n\n/**\n * Groups a set of endpoint specs (and optional nested routers) under a shared\n * URL prefix.\n *\n * The returned {@link RouterDef} can be passed directly to {@link createApi}\n * to produce a fully-typed API client. Nesting another {@link RouterDef} as a\n * value creates a sub-client whose prefix is the concatenation of both prefixes.\n *\n * @param prefix - Base path prepended to every endpoint's `path` (e.g. `'/users'`).\n * @param endpoints - Record of named {@link EndpointSpec}s or nested {@link RouterDef}s.\n *\n * @example\n * ```ts\n * // Flat router\n * export const todoRouter = defineRouter('/todos', {\n * getList: endpoint({ method: 'GET', path: '/', response: TodoListSchema }),\n * getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema }),\n * create: endpoint({ method: 'POST', path: '/', response: TodoSchema }),\n * });\n *\n * // Nested router — api.users.todos.getList() resolves to GET /users/todos/\n * export const userRouter = defineRouter('/users', {\n * getList: endpoint({ method: 'GET', path: '/', response: UserListSchema }),\n * todos: defineRouter('/todos', {\n * getList: endpoint({ method: 'GET', path: '/', response: TodoListSchema }),\n * getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema }),\n * }),\n * });\n * ```\n */\n/** Type guard — distinguishes a {@link RouterDef} from a leaf {@link EndpointSpec} at runtime. */\nexport function isRouterDef(entry: object): entry is RouterDef<RouterEndpoints> {\n return \"prefix\" in entry && \"endpoints\" in entry;\n}\n\nexport function defineRouter<TEndpoints extends RouterEndpoints>(\n prefix: string,\n endpoints: TEndpoints,\n): RouterDef<TEndpoints> {\n return { prefix, endpoints };\n}\n","/**\n * Joins URL path segments, normalising repeated slashes and trailing slashes.\n *\n * **Note:** Intended for relative API paths only. Absolute URLs containing\n * `://` will be collapsed (`https://` → `https:/`). Pass absolute URLs\n * directly to the executor instead of through this helper.\n */\nexport function joinPaths(...segments: string[]): string {\n const joined = segments\n .filter((s) => s !== \"\")\n .join(\"/\")\n .replace(/\\/+/g, \"/\");\n return joined.endsWith(\"/\") && joined.length > 1\n ? joined.slice(0, -1)\n : joined || \"/\";\n}\n\nexport function resolvePath(\n pathTemplate: string,\n params?: Record<string, unknown>,\n): string {\n if (!params) return pathTemplate;\n return pathTemplate.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, key) => {\n const value = params[key];\n if (value == null || value === \"\") throw new Error(`Missing path parameter: ${key}`);\n return encodeURIComponent(String(value));\n });\n}\n","export class ValidationError extends Error {\n readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"ValidationError\";\n if (cause !== undefined) {\n Object.defineProperty(this, \"cause\", {\n value: cause,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n }\n }\n}\n","import { isRouterDef } from \"./define-router.js\";\nimport type {\n CreateApiOptions,\n EndpointSpec,\n Executor,\n InferResponse,\n RequestShape,\n RouterDef,\n RouterEndpoints,\n ValidatorOutput,\n} from \"./types.js\";\nimport { joinPaths, resolvePath } from \"./utils/path.js\";\nimport { ValidationError } from \"./utils/validate.js\";\n\n/** Callable type for a single endpoint on the generated API client. */\ntype EndpointFn<TSpec extends EndpointSpec<any, any, any>> =\n TSpec[\"request\"] extends { parse: (data: unknown) => infer R }\n ? (params: R, signal?: AbortSignal) => Promise<InferResponse<TSpec>>\n : (\n params?: RequestShape,\n signal?: AbortSignal,\n ) => Promise<InferResponse<TSpec>>;\n\n/**\n * Fully-typed API client produced by {@link createApi}.\n * Nested {@link RouterDef} entries become nested sub-client objects.\n */\nexport type ApiClient<TEndpoints extends RouterEndpoints> = {\n [K in keyof TEndpoints]: TEndpoints[K] extends RouterDef<\n infer TNestedEndpoints\n >\n ? ApiClient<TNestedEndpoints>\n : TEndpoints[K] extends EndpointSpec<any, any, any>\n ? EndpointFn<TEndpoints[K]>\n : never;\n};\n\n/**\n * An {@link ApiClient} that also carries its source {@link RouterDef} on the\n * `$router` property. This is the actual return type of {@link createApi};\n * downstream tools (e.g. `@routar/react-query`) recover the router (prefix +\n * endpoint methods) from it without it being re-passed. `$router` is\n * non-enumerable and excluded from {@link ApiTypes}; the `$` prefix keeps it\n * from colliding with endpoint names.\n */\nexport type ApiClientWithRouter<TEndpoints extends RouterEndpoints> =\n ApiClient<TEndpoints> & { readonly $router: RouterDef<TEndpoints> };\n\n/**\n * Builds a fully-typed API client from an {@link Executor} and a router\n * (or bare endpoint map).\n *\n * Three call signatures are supported:\n * - `createApi(executor, router)` — preferred; pass the result of {@link defineRouter}.\n * - `createApi(executor, prefix, endpoints)` — inline router without {@link defineRouter}.\n * - `createApi(executor, endpoints)` — no prefix; useful for flat endpoint maps.\n *\n * Each key in `endpoints` becomes a typed async function on the returned client.\n * The function validates the request with `spec.request.parse` (if present),\n * resolves path parameters, calls the executor, validates the response with\n * `spec.response.parse`, and applies `spec.adapter` (if present).\n *\n * @param executor - Transport to use for every HTTP call.\n * @param router - A {@link RouterDef} produced by {@link defineRouter}.\n * @param options - Optional settings (e.g. `validate` to skip schema parsing in production).\n *\n * @example Basic usage\n * ```ts\n * const todoApi = createApi(executor, todoRouter);\n * const todos = await todoApi.getList({});\n * const todo = await todoApi.getDetail({ path: { id: 1 } });\n * const next = await todoApi.create({ body: { title: 'buy milk' } });\n * ```\n *\n * @example Nested router — access via dot notation\n * ```ts\n * const api = createApi(executor, apiRouter); // apiRouter has users → todos nesting\n * await api.users.getList({});\n * await api.users.todos.getList({});\n * ```\n *\n * @example Cancel in-flight requests with AbortSignal\n * ```ts\n * const controller = new AbortController();\n * const todos = await todoApi.getList({}, controller.signal);\n * controller.abort();\n * ```\n *\n * @example Extract types from the client — no duplication\n * ```ts\n * import type { ApiTypes } from '@routar/core';\n * type TodoApiTypes = ApiTypes<typeof todoApi>;\n * type Todo = TodoApiTypes['getDetail']['response'];\n * type CreateRequest = TodoApiTypes['create']['request'];\n * ```\n *\n * @example Skip response validation in production\n * ```ts\n * const prodApi = createApi(executor, todoRouter, {\n * validate: { request: true, response: process.env.NODE_ENV !== 'production' },\n * });\n * ```\n */\nexport function createApi<TEndpoints extends RouterEndpoints>(\n executor: Executor,\n router: RouterDef<TEndpoints>,\n options?: CreateApiOptions,\n): ApiClientWithRouter<TEndpoints>;\n\n/**\n * @param executor - Transport to use for every HTTP call.\n * @param prefix - URL prefix prepended to every endpoint path.\n * @param endpoints - Record of named endpoint specs.\n * @param options - Optional settings.\n */\nexport function createApi<TEndpoints extends RouterEndpoints>(\n executor: Executor,\n prefix: string,\n endpoints: TEndpoints,\n options?: CreateApiOptions,\n): ApiClientWithRouter<TEndpoints>;\n\n/**\n * @param executor - Transport to use for every HTTP call.\n * @param endpoints - Record of named endpoint specs (no URL prefix).\n * @param options - Optional settings.\n */\nexport function createApi<TEndpoints extends RouterEndpoints>(\n executor: Executor,\n endpoints: TEndpoints,\n options?: CreateApiOptions,\n): ApiClientWithRouter<TEndpoints>;\n\nexport function createApi(\n executor: Executor,\n routerOrPrefixOrEndpoints:\n | RouterDef<RouterEndpoints>\n | RouterEndpoints\n | string,\n endpointsArgOrOptions?: RouterEndpoints | CreateApiOptions,\n optionsArg?: CreateApiOptions,\n): Record<string, unknown> {\n const { prefix, endpoints, options } = resolveArgs(\n routerOrPrefixOrEndpoints,\n endpointsArgOrOptions,\n optionsArg,\n );\n const client = buildClient(executor, prefix, endpoints, options);\n // Stash the source router so downstream tools (e.g. @routar/react-query)\n // recover prefix + endpoint methods without it being re-passed.\n Object.defineProperty(client, \"$router\", {\n value: { prefix, endpoints } satisfies RouterDef<RouterEndpoints>,\n enumerable: false,\n });\n return client;\n}\n\nfunction resolveArgs(\n second: RouterDef<RouterEndpoints> | RouterEndpoints | string,\n third: RouterEndpoints | CreateApiOptions | undefined,\n fourth: CreateApiOptions | undefined,\n): {\n prefix: string;\n endpoints: RouterEndpoints;\n options: CreateApiOptions | undefined;\n} {\n if (typeof second === \"string\") {\n if (!third)\n throw new Error(\"endpoints is required when prefix is provided\");\n return {\n prefix: second,\n endpoints: third as RouterEndpoints,\n options: fourth,\n };\n }\n if (isRouterDef(second)) {\n return {\n prefix: second.prefix,\n endpoints: second.endpoints,\n options: third as CreateApiOptions | undefined,\n };\n }\n return {\n prefix: \"\",\n endpoints: second as RouterEndpoints,\n options: third as CreateApiOptions | undefined,\n };\n}\n\nfunction shouldValidate(\n options: CreateApiOptions | undefined,\n kind: \"request\" | \"response\",\n): boolean {\n const v = options?.validate;\n if (v === undefined || v === true) return true;\n if (v === false) return false;\n return v[kind] ?? true;\n}\n\nfunction buildClient(\n executor: Executor,\n prefix: string,\n endpoints: RouterEndpoints,\n options?: CreateApiOptions,\n): Record<string, unknown> {\n const client: Record<string, unknown> = {};\n\n for (const [key, entry] of Object.entries(endpoints)) {\n client[key] = isRouterDef(entry)\n ? buildClient(\n executor,\n joinPaths(prefix, entry.prefix),\n entry.endpoints,\n options,\n )\n : buildEndpointFn(\n executor,\n prefix,\n entry as EndpointSpec<any, any, any>,\n options,\n );\n }\n\n return client;\n}\n\nfunction buildEndpointFn(\n executor: Executor,\n prefix: string,\n spec: EndpointSpec<any, any, any>,\n options: CreateApiOptions | undefined,\n) {\n return async (params: RequestShape = {}, signal?: AbortSignal) => {\n let validatedParams: RequestShape = params;\n if (spec.request && shouldValidate(options, \"request\")) {\n try {\n validatedParams = spec.request.parse(params);\n } catch (err) {\n throw new ValidationError(\"Request validation failed\", err);\n }\n }\n\n const url = resolvePath(\n joinPaths(prefix, spec.path),\n validatedParams?.path,\n );\n\n const raw = await executor.execute({\n method: spec.method,\n url,\n params: validatedParams?.query as Record<string, unknown> | undefined,\n body: validatedParams?.body,\n signal,\n });\n\n let result: ValidatorOutput<typeof spec.response>;\n if (shouldValidate(options, \"response\")) {\n try {\n result = spec.response.parse(raw);\n } catch (err) {\n throw new ValidationError(\"Response validation failed\", err);\n }\n } else {\n result = raw;\n }\n\n return spec.adapter ? spec.adapter(result) : result;\n };\n}\n","import type {\n CreateExecutorOptions,\n ExecuteOptions,\n Executor,\n ExecutorMiddleware,\n ExecutorPlugin,\n} from \"./types.js\";\n\nfunction pluginToMiddleware(plugin: ExecutorPlugin): ExecutorMiddleware {\n return async (opts, next) => {\n const resolvedOpts = plugin.onRequest ? await plugin.onRequest(opts) : opts;\n try {\n const response = await next(resolvedOpts);\n return plugin.onResponse\n ? await plugin.onResponse(response, resolvedOpts)\n : response;\n } catch (err) {\n if (plugin.onError) {\n await plugin.onError(err, resolvedOpts);\n throw err;\n }\n throw err;\n }\n };\n}\n\nexport function buildChain(\n execute: (options: ExecuteOptions) => Promise<unknown>,\n middlewares: ExecutorMiddleware[],\n): (options: ExecuteOptions) => Promise<unknown> {\n return middlewares.reduceRight<(options: ExecuteOptions) => Promise<unknown>>(\n (next, mw) => (opts) => mw(opts, next),\n execute,\n );\n}\n\n/**\n * Creates an {@link Executor} by wrapping a transport function with plugins.\n *\n * Plugins run in declaration order (first plugin is outermost).\n *\n * For `retry` and `timeout`, use the options on {@link createFetchExecutor}\n * or configure them via the underlying HTTP client (axios, ky).\n *\n * @example\n * ```ts\n * const executor = createExecutor(transport, {\n * plugins: [authPlugin, logger()],\n * });\n * ```\n *\n * @example Unwrap an envelope response\n * ```ts\n * const executor = createExecutor(transport, {\n * unwrap: (raw) => (raw as { data: unknown })?.data ?? raw,\n * });\n * ```\n */\nexport function createExecutor(\n execute: (options: ExecuteOptions) => Promise<unknown>,\n options: CreateExecutorOptions = {},\n): Executor {\n const plugins = [...(options.plugins ?? [])];\n // `unwrap` becomes the innermost `onResponse` hook: appended last so it runs\n // first on the response (immediately after the transport, before user plugins).\n if (options.unwrap) {\n const unwrapFn = options.unwrap;\n plugins.push({ onResponse: (raw) => unwrapFn(raw) });\n }\n const middlewares = plugins.map(pluginToMiddleware);\n return { execute: buildChain(execute, middlewares) };\n}\n\n/**\n * Creates an {@link Executor} that selects the underlying transport at\n * request time based on the result of `resolver`.\n *\n * @example\n * ```ts\n * const apiExecutor = dispatchExecutor(() =>\n * typeof window === 'undefined' ? serverExecutor : clientExecutor,\n * );\n * ```\n */\nexport function dispatchExecutor(\n resolver: (opts: ExecuteOptions) => Executor,\n): Executor {\n return {\n execute: (opts) => resolver(opts).execute(opts),\n };\n}\n","import type { ExecuteOptions, ExecutorMiddleware, ExecutorPlugin } from \"./types.js\";\n\n/**\n * Thrown by the built-in `timeout` option when a request exceeds the\n * configured duration. Distinguishable from a user-initiated\n * {@link AbortSignal} cancellation.\n */\nexport class TimeoutError extends Error {\n constructor(public readonly ms: number) {\n super(`Request timed out after ${ms}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\n/**\n * Identity helper that returns the plugin as-is, providing full type inference.\n *\n * @example\n * ```ts\n * const authPlugin = definePlugin({\n * name: 'auth',\n * onRequest: async (opts) => ({\n * ...opts,\n * headers: { ...opts.headers, Authorization: `Bearer ${await getToken()}` },\n * }),\n * });\n * ```\n */\nexport function definePlugin(plugin: ExecutorPlugin): ExecutorPlugin {\n return plugin;\n}\n\n/**\n * Logs each request and its outcome (success duration or error).\n *\n * @param options.log - Custom logging function. Defaults to `console.log`.\n *\n * @example\n * ```ts\n * createExecutor(transport, {\n * plugins: [logger({ log: (msg, data) => myLogger.debug(msg, data) })],\n * })\n * ```\n */\nexport function logger(options?: {\n log?: (message: string, data?: unknown) => void;\n}): ExecutorPlugin {\n const log = options?.log ?? ((msg, data) => console.log(msg, data));\n const timings = new WeakMap<ExecuteOptions, number>();\n return definePlugin({\n name: \"logger\",\n onRequest: (opts) => {\n timings.set(opts, Date.now());\n log(`[routar] ${opts.method} ${opts.url}`, {\n params: opts.params,\n body: opts.body,\n });\n return opts;\n },\n onResponse: (res, opts) => {\n log(\n `[routar] ${opts.method} ${opts.url} — ${Date.now() - (timings.get(opts) ?? Date.now())}ms`,\n );\n timings.delete(opts);\n return res;\n },\n onError: (err, opts) => {\n log(\n `[routar] ${opts.method} ${opts.url} — error after ${Date.now() - (timings.get(opts) ?? Date.now())}ms`,\n err,\n );\n timings.delete(opts);\n throw err as never;\n },\n });\n}\n\nexport function withRetry(\n count: number,\n options?: { shouldRetry?: (error: unknown, attempt: number) => boolean },\n): ExecutorMiddleware {\n return async (opts, next) => {\n let lastError: unknown;\n for (let attempt = 0; attempt <= count; attempt++) {\n try {\n return await next(opts);\n } catch (err) {\n lastError = err;\n if (attempt === count) break;\n if (options?.shouldRetry && !options.shouldRetry(err, attempt)) break;\n }\n }\n throw lastError;\n };\n}\n\nexport function withTimeout(ms: number): ExecutorMiddleware {\n return async (opts, next) => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(new TimeoutError(ms)), ms);\n\n const { signal, cleanup } = opts.signal\n ? anySignal([opts.signal, controller.signal])\n : { signal: controller.signal, cleanup: () => {} };\n\n try {\n return await next({ ...opts, signal });\n } finally {\n clearTimeout(timer);\n cleanup();\n }\n };\n}\n\nfunction anySignal(signals: AbortSignal[]): {\n signal: AbortSignal;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n const onAbort = () => controller.abort();\n const attached: AbortSignal[] = [];\n for (const s of signals) {\n if (s.aborted) {\n controller.abort();\n break;\n }\n s.addEventListener(\"abort\", onAbort, { once: true });\n attached.push(s);\n }\n return {\n signal: controller.signal,\n cleanup: () =>\n attached.forEach((s) => {\n s.removeEventListener(\"abort\", onAbort);\n }),\n };\n}\n","export function serializeParams(\n params: Record<string, unknown>,\n): URLSearchParams {\n const result = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item != null) result.append(key, String(item));\n }\n } else if (typeof value === \"object\") {\n throw new TypeError(\n `serializeParams: value for key \"${key}\" is a plain object. Serialize it to a string before passing as a query parameter.`,\n );\n } else {\n result.append(key, String(value));\n }\n }\n return result;\n}\n","import type { CreateExecutorOptions, ExecuteOptions, Executor, ExecutorMiddleware } from \"./types.js\";\nimport { buildChain, createExecutor } from \"./create-executor.js\";\nimport { withRetry, withTimeout } from \"./middleware.js\";\nimport { serializeParams } from \"./utils/params.js\";\n\nexport type FetchRetryOption =\n | number\n | { count: number; shouldRetry?: (error: unknown, attempt: number) => boolean };\n\nexport interface FetchExecutorOptions extends CreateExecutorOptions {\n defaultHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n retry?: FetchRetryOption;\n timeout?: number;\n}\n\nfunction buildFetchChain(\n transport: (opts: ExecuteOptions) => Promise<unknown>,\n retry?: FetchRetryOption,\n timeout?: number,\n): (opts: ExecuteOptions) => Promise<unknown> {\n const middlewares: ExecutorMiddleware[] = [\n ...(retry != null\n ? [\n typeof retry === \"number\"\n ? withRetry(retry)\n : withRetry(retry.count, { shouldRetry: retry.shouldRetry }),\n ]\n : []),\n ...(timeout != null ? [withTimeout(timeout)] : []),\n ];\n if (middlewares.length === 0) return transport;\n return buildChain(transport, middlewares);\n}\n\n/**\n * Creates an {@link Executor} backed by the browser / Node.js `fetch` API.\n *\n * Suited for SSR environments where you need per-request dynamic headers\n * (e.g. forwarding auth cookies) without sharing state across requests.\n *\n * - Query params are serialized and appended to the URL.\n * - A `Content-Type: application/json` header is added automatically when\n * a request body is present.\n * - Responses with status 204 or `Content-Length: 0` resolve to `null`.\n * - Non-2xx responses throw an {@link HttpError}.\n *\n * @param baseURL - Absolute base URL prepended to every endpoint path.\n * Accepts a static string or a sync/async factory called on every request —\n * useful when the origin depends on runtime environment (e.g. SSR vs CSR).\n * @param options.defaultHeaders - Async factory called on every request to\n * produce headers (e.g. reading cookies in a Next.js server component).\n * @param options.plugins - Plugins applied around the fetch call. Each retry\n * attempt re-runs `onRequest` hooks (so headers are refreshed per attempt)\n * and `onError` hooks. Token-refresh-on-401 patterns work by updating an\n * external token store in `onError` and letting `onRequest` pick it up on\n * the next attempt.\n * @param options.retry - Number of retries, or `{ count, shouldRetry? }`.\n * @param options.timeout - Per-attempt timeout in milliseconds.\n *\n * @example Minimal — no options needed\n * ```ts\n * const executor = createFetchExecutor('https://api.example.com');\n * ```\n *\n * @example Dynamic base URL for SSR/CSR\n * ```ts\n * const executor = createFetchExecutor(\n * () => typeof window === 'undefined' ? 'http://localhost:3000/api' : '/api',\n * );\n * ```\n *\n * @example SSR with bearer token\n * ```ts\n * const executor = createFetchExecutor('https://api.example.com', {\n * defaultHeaders: async () => {\n * const token = await getServerToken();\n * return token ? { Authorization: `Bearer ${token}` } : {};\n * },\n * });\n * ```\n *\n * @example With retry and timeout\n * ```ts\n * const executor = createFetchExecutor('https://api.example.com', {\n * retry: 2,\n * timeout: 8_000,\n * });\n * ```\n */\nexport function createFetchExecutor(\n baseURL: string | (() => string | Promise<string>),\n options?: FetchExecutorOptions,\n): Executor {\n const transport = async ({\n method,\n url,\n params,\n body,\n headers,\n signal,\n }: ExecuteOptions) => {\n const resolvedBase = typeof baseURL === \"function\" ? await baseURL() : baseURL;\n const fullURL = new URL(resolvedBase.replace(/\\/$/, \"\") + url);\n if (params) {\n serializeParams(params).forEach((v, k) => {\n fullURL.searchParams.set(k, v);\n });\n }\n\n const defaultHeaders = (await options?.defaultHeaders?.()) ?? {};\n\n const res = await fetch(fullURL.toString(), {\n method,\n headers: {\n ...defaultHeaders,\n ...headers,\n ...(body != null ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: body != null ? JSON.stringify(body) : undefined,\n signal,\n });\n\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null);\n throw new HttpError(res.status, res.statusText, errorBody, { url: fullURL.toString(), method });\n }\n if (res.status === 204 || res.status === 205 || res.status === 304) {\n return null;\n }\n const text = await res.text();\n return text === \"\" ? null : JSON.parse(text);\n };\n\n const executor = createExecutor(transport, { plugins: options?.plugins, unwrap: options?.unwrap });\n return { execute: buildFetchChain(executor.execute, options?.retry, options?.timeout) };\n}\n\n/**\n * Thrown by {@link createFetchExecutor} when the server returns a non-2xx\n * status code. The Axios and ky executors also normalize their transport\n * errors to `HttpError`, so `onError` plugins and callers can branch on a\n * single error type regardless of the underlying transport. The original\n * transport error (e.g. an `AxiosError` or ky `HTTPError`) is preserved on\n * the `cause` property.\n *\n * @example\n * ```ts\n * try {\n * await api.getDetail({ path: { id: 999 } });\n * } catch (err) {\n * if (err instanceof HttpError && err.status === 404) {\n * // handle not-found\n * }\n * }\n * ```\n */\nexport class HttpError extends Error {\n public readonly url?: string;\n public readonly method?: string;\n\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body: unknown = null,\n options?: { url?: string; method?: string; cause?: unknown },\n ) {\n super(`HTTP ${status}: ${statusText}`, { cause: options?.cause });\n this.name = \"HttpError\";\n this.url = options?.url;\n this.method = options?.method;\n }\n}\n","import type {\n HttpMethod,\n RequestShape,\n Validator,\n ValidatorOutput,\n} from \"./types.js\";\n\n/**\n * Extracts `:param` segment names from a path template string as a union of\n * string literals.\n *\n * @example\n * ```ts\n * type P = PathParams<'/:userId/posts/:postId'>; // 'userId' | 'postId'\n * ```\n */\nexport type PathParams<TPath extends string> =\n TPath extends `${string}:${infer Param}/${infer Rest}`\n ? Param | PathParams<Rest>\n : TPath extends `${string}:${infer Param}`\n ? Param\n : never;\n\n/**\n * When `TPath` contains dynamic segments (`:param`), requires `request.path`\n * to include all extracted param names. No constraint for static paths.\n */\ntype PathConstraint<TPath extends string> = [PathParams<TPath>] extends [never]\n ? {}\n : { path: Record<PathParams<TPath>, unknown> };\n\n/**\n * Type-safe endpoint definition helper.\n *\n * Use this instead of a plain object literal to get full type inference on\n * `adapter` without requiring explicit annotations or `as any` casts.\n * The four overloads cover every combination of optional `request` validator\n * and optional `adapter` function while keeping all return-type fields\n * required so that TypeScript can narrow them downstream.\n *\n * When `path` contains dynamic segments (e.g. `'/:id'`), TypeScript enforces\n * that `request` includes a matching `path` field with those param names.\n * A mismatch or missing key is a compile-time error.\n *\n * The literal HTTP method (`'GET'`, `'POST'`, …) is preserved on the return\n * type — `endpoint({ method: 'GET', ... }).method` is typed `'GET'`, not the\n * `HttpMethod` union.\n *\n * @example Basic GET with no params\n * ```ts\n * const getList = endpoint({ method: 'GET', path: '/', response: z.array(TodoSchema) });\n * ```\n *\n * @example GET with query params\n * ```ts\n * const search = endpoint({\n * method: 'GET',\n * path: '/search',\n * request: z.object({ query: z.object({ q: z.string(), limit: z.number().optional() }) }),\n * response: z.array(TodoSchema),\n * });\n * ```\n *\n * @example POST with body\n * ```ts\n * const create = endpoint({\n * method: 'POST',\n * path: '/',\n * request: z.object({ body: z.object({ title: z.string() }) }),\n * response: TodoSchema,\n * });\n * ```\n *\n * @example Adapter — raw is inferred from the response schema, no cast needed\n * ```ts\n * const getDetail = endpoint({\n * method: 'GET',\n * path: '/:id',\n * request: z.object({ path: z.object({ id: z.number() }) }),\n * response: TodoRawSchema,\n * adapter: (raw) => ({ ...raw, label: `#${raw.id} ${raw.title}` }),\n * });\n * ```\n *\n * @example Path param enforcement\n * ```ts\n * // ✅ path has ':id' → request.path.id is required\n * const getDetail = endpoint({\n * method: 'GET',\n * path: '/:id',\n * request: z.object({ path: z.object({ id: z.number() }) }),\n * response: TodoSchema,\n * });\n *\n * // ❌ compile error — 'id' is missing from request.path\n * const broken = endpoint({\n * method: 'GET',\n * path: '/:id',\n * request: z.object({ query: z.object({ foo: z.string() }) }),\n * response: TodoSchema,\n * });\n * ```\n */\n// request O + adapter O\nexport function endpoint<\n TMethod extends HttpMethod,\n TPath extends string,\n TRequest extends RequestShape & PathConstraint<TPath>,\n TResponse extends Validator<unknown>,\n TOut,\n>(spec: {\n method: TMethod;\n path: TPath;\n request: Validator<TRequest>;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n}): {\n method: TMethod;\n path: string;\n request: Validator<TRequest>;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n};\n\n// request O + adapter X\nexport function endpoint<\n TMethod extends HttpMethod,\n TPath extends string,\n TRequest extends RequestShape & PathConstraint<TPath>,\n TResponse extends Validator<unknown>,\n>(spec: {\n method: TMethod;\n path: TPath;\n request: Validator<TRequest>;\n response: TResponse;\n}): {\n method: TMethod;\n path: string;\n request: Validator<TRequest>;\n response: TResponse;\n};\n\n// request X + adapter O\nexport function endpoint<\n TMethod extends HttpMethod,\n TResponse extends Validator<unknown>,\n TOut,\n>(spec: {\n method: TMethod;\n path: string;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n}): {\n method: TMethod;\n path: string;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n};\n\n// request X + adapter X\nexport function endpoint<\n TMethod extends HttpMethod,\n TResponse extends Validator<unknown>,\n>(spec: {\n method: TMethod;\n path: string;\n response: TResponse;\n}): {\n method: TMethod;\n path: string;\n response: TResponse;\n};\n\nexport function endpoint(spec: unknown): unknown {\n return spec;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/define-router.ts","../src/middleware.ts","../src/utils/path.ts","../src/utils/validate.ts","../src/utils/run-validator.ts","../src/create-api.ts","../src/create-executor.ts","../src/utils/params.ts","../src/create-fetch-executor.ts","../src/utils/compose-request.ts","../src/define-endpoint.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { RouterDef, RouterEndpoints } from \"./types.js\";\n\n/**\n * Groups a set of endpoint specs (and optional nested routers) under a shared\n * URL prefix.\n *\n * The returned {@link RouterDef} can be passed directly to {@link createApi}\n * to produce a fully-typed API client. Nesting another {@link RouterDef} as a\n * value creates a sub-client whose prefix is the concatenation of both prefixes.\n *\n * @param prefix - Base path prepended to every endpoint's `path` (e.g. `'/users'`).\n * @param endpoints - Record of named {@link EndpointSpec}s or nested {@link RouterDef}s.\n *\n * @example\n * ```ts\n * // Flat router\n * export const todoRouter = defineRouter('/todos', {\n * getList: endpoint({ method: 'GET', path: '/', response: TodoListSchema }),\n * getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema }),\n * create: endpoint({ method: 'POST', path: '/', response: TodoSchema }),\n * });\n *\n * // Nested router — api.users.todos.getList() resolves to GET /users/todos/\n * export const userRouter = defineRouter('/users', {\n * getList: endpoint({ method: 'GET', path: '/', response: UserListSchema }),\n * todos: defineRouter('/todos', {\n * getList: endpoint({ method: 'GET', path: '/', response: TodoListSchema }),\n * getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema }),\n * }),\n * });\n * ```\n */\n/** Type guard — distinguishes a {@link RouterDef} from a leaf {@link EndpointSpec} at runtime. */\nexport function isRouterDef(entry: object): entry is RouterDef<RouterEndpoints> {\n return \"prefix\" in entry && \"endpoints\" in entry;\n}\n\nexport function defineRouter<TEndpoints extends RouterEndpoints>(\n prefix: string,\n endpoints: TEndpoints,\n): RouterDef<TEndpoints> {\n return { prefix, endpoints };\n}\n","import type { ExecuteOptions, ExecutorMiddleware, ExecutorPlugin } from \"./types.js\";\n\n/**\n * Thrown by the built-in `timeout` option when a request exceeds the\n * configured duration. Distinguishable from a user-initiated\n * {@link AbortSignal} cancellation.\n */\nexport class TimeoutError extends Error {\n constructor(public readonly ms: number) {\n super(`Request timed out after ${ms}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\n/**\n * Identity helper that returns the plugin as-is, providing full type inference.\n *\n * @example\n * ```ts\n * const authPlugin = definePlugin({\n * name: 'auth',\n * onRequest: async (opts) => ({\n * ...opts,\n * headers: { ...opts.headers, Authorization: `Bearer ${await getToken()}` },\n * }),\n * });\n * ```\n */\nexport function definePlugin(plugin: ExecutorPlugin): ExecutorPlugin {\n return plugin;\n}\n\n/**\n * Logs each request and its outcome (success duration or error).\n *\n * @param options.log - Custom logging function. Defaults to `console.log`.\n *\n * @example\n * ```ts\n * createExecutor(transport, {\n * plugins: [logger({ log: (msg, data) => myLogger.debug(msg, data) })],\n * })\n * ```\n */\nexport function logger(options?: {\n log?: (message: string, data?: unknown) => void;\n}): ExecutorPlugin {\n const log = options?.log ?? ((msg, data) => console.log(msg, data));\n const timings = new WeakMap<ExecuteOptions, number>();\n return definePlugin({\n name: \"logger\",\n onRequest: (opts) => {\n timings.set(opts, Date.now());\n log(`[routar] ${opts.method} ${opts.url}`, {\n params: opts.params,\n body: opts.body,\n });\n return opts;\n },\n onResponse: (res, opts) => {\n log(\n `[routar] ${opts.method} ${opts.url} — ${Date.now() - (timings.get(opts) ?? Date.now())}ms`,\n );\n timings.delete(opts);\n return res;\n },\n onError: (err, opts) => {\n log(\n `[routar] ${opts.method} ${opts.url} — error after ${Date.now() - (timings.get(opts) ?? Date.now())}ms`,\n err,\n );\n timings.delete(opts);\n throw err as never;\n },\n });\n}\n\nexport function withRetry(\n count: number,\n options?: { shouldRetry?: (error: unknown, attempt: number) => boolean },\n): ExecutorMiddleware {\n return async (opts, next) => {\n let lastError: unknown;\n for (let attempt = 0; attempt <= count; attempt++) {\n try {\n return await next(opts);\n } catch (err) {\n lastError = err;\n if (attempt === count) break;\n if (options?.shouldRetry && !options.shouldRetry(err, attempt)) break;\n }\n }\n throw lastError;\n };\n}\n\nexport function withTimeout(ms: number): ExecutorMiddleware {\n return async (opts, next) => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(new TimeoutError(ms)), ms);\n\n const { signal, cleanup } = opts.signal\n ? anySignal([opts.signal, controller.signal])\n : { signal: controller.signal, cleanup: () => {} };\n\n try {\n return await next({ ...opts, signal });\n } finally {\n clearTimeout(timer);\n cleanup();\n }\n };\n}\n\nfunction anySignal(signals: AbortSignal[]): {\n signal: AbortSignal;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n const onAbort = () => controller.abort();\n const attached: AbortSignal[] = [];\n for (const s of signals) {\n if (s.aborted) {\n controller.abort();\n break;\n }\n s.addEventListener(\"abort\", onAbort, { once: true });\n attached.push(s);\n }\n return {\n signal: controller.signal,\n cleanup: () =>\n attached.forEach((s) => {\n s.removeEventListener(\"abort\", onAbort);\n }),\n };\n}\n","/**\n * Joins URL path segments, normalising repeated slashes and trailing slashes.\n *\n * **Note:** Intended for relative API paths only. Absolute URLs containing\n * `://` will be collapsed (`https://` → `https:/`). Pass absolute URLs\n * directly to the executor instead of through this helper.\n */\nexport function joinPaths(...segments: string[]): string {\n const joined = segments\n .filter((s) => s !== \"\")\n .join(\"/\")\n .replace(/\\/+/g, \"/\");\n return joined.endsWith(\"/\") && joined.length > 1\n ? joined.slice(0, -1)\n : joined || \"/\";\n}\n\nexport function resolvePath(\n pathTemplate: string,\n params?: Record<string, unknown>,\n): string {\n if (!params) return pathTemplate;\n return pathTemplate.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, key) => {\n const value = params[key];\n if (value == null || value === \"\") throw new Error(`Missing path parameter: ${key}`);\n return encodeURIComponent(String(value));\n });\n}\n","import type { StandardSchemaV1 } from \"../standard-schema.js\";\n\nexport class ValidationError extends Error {\n readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"ValidationError\";\n if (cause !== undefined) {\n Object.defineProperty(this, \"cause\", {\n value: cause,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n }\n }\n}\n\n/**\n * Thrown internally when a Standard Schema validator (`~standard.validate`)\n * reports issues. It is wrapped as the `cause` of a {@link ValidationError} by\n * the API client, mirroring how a thrown `.parse()` error becomes the cause —\n * so callers branch on `ValidationError` regardless of the validator library.\n * The structured `issues` array is preserved for drift reporting.\n */\nexport class StandardSchemaError extends Error {\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n issues.map((issue) => issue.message).join(\"; \") ||\n \"Standard Schema validation failed\",\n );\n this.name = \"StandardSchemaError\";\n this.issues = issues;\n }\n}\n","import type { StandardSchemaV1 } from \"../standard-schema.js\";\nimport type { AnyValidator, Validator } from \"../types.js\";\nimport { StandardSchemaError } from \"./validate.js\";\n\n/**\n * Validates `data` with an {@link AnyValidator}, returning the parsed value.\n *\n * Prefers the synchronous `.parse()` path when present (Zod, Valibot, Yup, or\n * any object with `.parse()`); a thrown parse error propagates unchanged.\n * Otherwise uses the Standard Schema `~standard.validate` path (sync or async);\n * reported issues are thrown as a {@link StandardSchemaError}.\n *\n * Exported for executor / mock-handler authors who need routar's exact\n * validate-or-throw semantics for both validator styles.\n */\nexport async function runValidator(\n validator: AnyValidator,\n data: unknown,\n): Promise<unknown> {\n if (typeof (validator as Validator<unknown>).parse === \"function\") {\n return (validator as Validator<unknown>).parse(data);\n }\n const standard = (validator as StandardSchemaV1)[\"~standard\"];\n const result = await standard.validate(data);\n if (result.issues) throw new StandardSchemaError(result.issues);\n return result.value;\n}\n","import { isRouterDef } from \"./define-router.js\";\nimport { TimeoutError } from \"./middleware.js\";\nimport type { StandardSchemaV1 } from \"./standard-schema.js\";\nimport type {\n CreateApiOptions,\n EndpointCallOptions,\n EndpointSpec,\n ExecuteOptions,\n Executor,\n InferResponse,\n RequestShape,\n RouterDef,\n RouterEndpoints,\n Validator,\n ValidatorOutput,\n} from \"./types.js\";\nimport { joinPaths, resolvePath } from \"./utils/path.js\";\nimport { runValidator } from \"./utils/run-validator.js\";\nimport { ValidationError } from \"./utils/validate.js\";\n\n/** Callable type for a single endpoint on the generated API client. */\ntype EndpointFn<TSpec extends EndpointSpec<any, any, any>> =\n TSpec[\"request\"] extends Validator<infer R>\n ? (\n params: R,\n options?: AbortSignal | EndpointCallOptions,\n ) => Promise<InferResponse<TSpec>>\n : TSpec[\"request\"] extends StandardSchemaV1<unknown, infer O>\n ? (\n params: O,\n options?: AbortSignal | EndpointCallOptions,\n ) => Promise<InferResponse<TSpec>>\n : (\n params?: RequestShape,\n options?: AbortSignal | EndpointCallOptions,\n ) => Promise<InferResponse<TSpec>>;\n\n/**\n * Fully-typed API client produced by {@link createApi}.\n * Nested {@link RouterDef} entries become nested sub-client objects.\n */\nexport type ApiClient<TEndpoints extends RouterEndpoints> = {\n [K in keyof TEndpoints]: TEndpoints[K] extends RouterDef<\n infer TNestedEndpoints\n >\n ? ApiClient<TNestedEndpoints>\n : TEndpoints[K] extends EndpointSpec<any, any, any>\n ? EndpointFn<TEndpoints[K]>\n : never;\n};\n\n/**\n * An {@link ApiClient} that also carries its source {@link RouterDef} on the\n * `$router` property. This is the actual return type of {@link createApi};\n * downstream tools (e.g. `@routar/react-query`) recover the router (prefix +\n * endpoint methods) from it without it being re-passed. `$router` is\n * non-enumerable and excluded from {@link ApiTypes}; the `$` prefix keeps it\n * from colliding with endpoint names.\n */\nexport type ApiClientWithRouter<TEndpoints extends RouterEndpoints> =\n ApiClient<TEndpoints> & { readonly $router: RouterDef<TEndpoints> };\n\n/**\n * Builds a fully-typed API client from an {@link Executor} and a router\n * (or bare endpoint map).\n *\n * Three call signatures are supported:\n * - `createApi(executor, router)` — preferred; pass the result of {@link defineRouter}.\n * - `createApi(executor, prefix, endpoints)` — inline router without {@link defineRouter}.\n * - `createApi(executor, endpoints)` — no prefix; useful for flat endpoint maps.\n *\n * Each key in `endpoints` becomes a typed async function on the returned client.\n * The function validates the request with `spec.request.parse` (if present),\n * resolves path parameters, calls the executor, validates the response with\n * `spec.response.parse`, and applies `spec.adapter` (if present).\n *\n * @param executor - Transport to use for every HTTP call.\n * @param router - A {@link RouterDef} produced by {@link defineRouter}.\n * @param options - Optional settings (e.g. `validate` to skip schema parsing in production).\n *\n * @example Basic usage\n * ```ts\n * const todoApi = createApi(executor, todoRouter);\n * const todos = await todoApi.getList({});\n * const todo = await todoApi.getDetail({ path: { id: 1 } });\n * const next = await todoApi.create({ body: { title: 'buy milk' } });\n * ```\n *\n * @example Nested router — access via dot notation\n * ```ts\n * const api = createApi(executor, apiRouter); // apiRouter has users → todos nesting\n * await api.users.getList({});\n * await api.users.todos.getList({});\n * ```\n *\n * @example Cancel in-flight requests with AbortSignal\n * ```ts\n * const controller = new AbortController();\n * const todos = await todoApi.getList({}, controller.signal);\n * controller.abort();\n * ```\n *\n * @example Extract types from the client — no duplication\n * ```ts\n * import type { ApiTypes } from '@routar/core';\n * type TodoApiTypes = ApiTypes<typeof todoApi>;\n * type Todo = TodoApiTypes['getDetail']['response'];\n * type CreateRequest = TodoApiTypes['create']['request'];\n * ```\n *\n * @example Skip response validation in production\n * ```ts\n * const prodApi = createApi(executor, todoRouter, {\n * validate: { request: true, response: process.env.NODE_ENV !== 'production' },\n * });\n * ```\n */\nexport function createApi<TEndpoints extends RouterEndpoints>(\n executor: Executor,\n router: RouterDef<TEndpoints>,\n options?: CreateApiOptions,\n): ApiClientWithRouter<TEndpoints>;\n\n/**\n * @param executor - Transport to use for every HTTP call.\n * @param prefix - URL prefix prepended to every endpoint path.\n * @param endpoints - Record of named endpoint specs.\n * @param options - Optional settings.\n */\nexport function createApi<TEndpoints extends RouterEndpoints>(\n executor: Executor,\n prefix: string,\n endpoints: TEndpoints,\n options?: CreateApiOptions,\n): ApiClientWithRouter<TEndpoints>;\n\n/**\n * @param executor - Transport to use for every HTTP call.\n * @param endpoints - Record of named endpoint specs (no URL prefix).\n * @param options - Optional settings.\n */\nexport function createApi<TEndpoints extends RouterEndpoints>(\n executor: Executor,\n endpoints: TEndpoints,\n options?: CreateApiOptions,\n): ApiClientWithRouter<TEndpoints>;\n\nexport function createApi(\n executor: Executor,\n routerOrPrefixOrEndpoints:\n | RouterDef<RouterEndpoints>\n | RouterEndpoints\n | string,\n endpointsArgOrOptions?: RouterEndpoints | CreateApiOptions,\n optionsArg?: CreateApiOptions,\n): Record<string, unknown> {\n const { prefix, endpoints, options } = resolveArgs(\n routerOrPrefixOrEndpoints,\n endpointsArgOrOptions,\n optionsArg,\n );\n const client = buildClient(executor, prefix, endpoints, options);\n // Stash the source router so downstream tools (e.g. @routar/react-query)\n // recover prefix + endpoint methods without it being re-passed.\n Object.defineProperty(client, \"$router\", {\n value: { prefix, endpoints } satisfies RouterDef<RouterEndpoints>,\n enumerable: false,\n });\n return client;\n}\n\nfunction resolveArgs(\n second: RouterDef<RouterEndpoints> | RouterEndpoints | string,\n third: RouterEndpoints | CreateApiOptions | undefined,\n fourth: CreateApiOptions | undefined,\n): {\n prefix: string;\n endpoints: RouterEndpoints;\n options: CreateApiOptions | undefined;\n} {\n if (typeof second === \"string\") {\n if (!third)\n throw new Error(\"endpoints is required when prefix is provided\");\n return {\n prefix: second,\n endpoints: third as RouterEndpoints,\n options: fourth,\n };\n }\n if (isRouterDef(second)) {\n return {\n prefix: second.prefix,\n endpoints: second.endpoints,\n options: third as CreateApiOptions | undefined,\n };\n }\n return {\n prefix: \"\",\n endpoints: second as RouterEndpoints,\n options: third as CreateApiOptions | undefined,\n };\n}\n\ntype ResolvedMode = \"on\" | \"off\" | \"warn\";\n\nfunction validateMode(\n options: CreateApiOptions | undefined,\n kind: \"request\" | \"response\",\n): ResolvedMode {\n const v = options?.validate;\n const mode =\n v === undefined\n ? true\n : typeof v === \"boolean\" || v === \"warn\"\n ? v\n : (v[kind] ?? true);\n return mode === \"warn\" ? \"warn\" : mode ? \"on\" : \"off\";\n}\n\nfunction buildClient(\n executor: Executor,\n prefix: string,\n endpoints: RouterEndpoints,\n options?: CreateApiOptions,\n): Record<string, unknown> {\n const client: Record<string, unknown> = {};\n\n for (const [key, entry] of Object.entries(endpoints)) {\n client[key] = isRouterDef(entry)\n ? buildClient(\n executor,\n joinPaths(prefix, entry.prefix),\n entry.endpoints,\n options,\n )\n : buildEndpointFn(\n executor,\n prefix,\n entry as EndpointSpec<any, any, any>,\n options,\n );\n }\n\n return client;\n}\n\nfunction buildEndpointFn(\n executor: Executor,\n prefix: string,\n spec: EndpointSpec<any, any, any>,\n options: CreateApiOptions | undefined,\n) {\n return async (\n params: RequestShape = {},\n signalOrOptions?: AbortSignal | EndpointCallOptions,\n ) => {\n const call = normalizeCallOptions(signalOrOptions);\n const reqMode = validateMode(options, \"request\");\n let validatedParams: RequestShape = params;\n let requestError: ValidationError | null = null;\n if (spec.request && reqMode !== \"off\") {\n try {\n validatedParams = (await runValidator(\n spec.request,\n params,\n )) as RequestShape;\n } catch (err) {\n requestError = new ValidationError(\"Request validation failed\", err);\n if (reqMode !== \"warn\") throw requestError;\n // 'warn': pass the raw params through and report the drift below.\n validatedParams = params;\n }\n }\n\n const url = resolvePath(\n joinPaths(prefix, spec.path),\n validatedParams?.path,\n );\n\n if (requestError) {\n options?.onValidationError?.(requestError, {\n kind: \"request\",\n method: spec.method,\n url,\n data: params,\n });\n }\n\n const raw = await executeWithTimeout(executor, call.timeout, {\n method: spec.method,\n url,\n params: validatedParams?.query as Record<string, unknown> | undefined,\n body: validatedParams?.body,\n headers: call.headers,\n signal: call.signal,\n });\n\n const resMode = validateMode(options, \"response\");\n let result: ValidatorOutput<typeof spec.response>;\n if (resMode === \"off\") {\n result = raw;\n } else {\n try {\n result = (await runValidator(\n spec.response,\n raw,\n )) as ValidatorOutput<typeof spec.response>;\n } catch (err) {\n const responseError = new ValidationError(\n \"Response validation failed\",\n err,\n );\n if (resMode !== \"warn\") throw responseError;\n // 'warn': report the drift and pass the raw response through.\n options?.onValidationError?.(responseError, {\n kind: \"response\",\n method: spec.method,\n url,\n data: raw,\n });\n result = raw;\n }\n }\n\n return spec.adapter ? spec.adapter(result) : result;\n };\n}\n\n/**\n * Normalizes the optional second endpoint argument. A bare {@link AbortSignal}\n * (the legacy form) is wrapped as `{ signal }`; an options object is returned\n * as-is. Detection uses `instanceof AbortSignal` with a duck-typed fallback for\n * runtimes/polyfills where the global identity differs.\n */\nfunction normalizeCallOptions(\n arg: AbortSignal | EndpointCallOptions | undefined,\n): EndpointCallOptions {\n if (arg == null) return {};\n if (isAbortSignal(arg)) return { signal: arg };\n return arg;\n}\n\nfunction isAbortSignal(value: unknown): value is AbortSignal {\n if (typeof AbortSignal !== \"undefined\" && value instanceof AbortSignal) {\n return true;\n }\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AbortSignal).aborted === \"boolean\" &&\n typeof (value as AbortSignal).addEventListener === \"function\"\n );\n}\n\n/**\n * Runs the executor, applying a per-call timeout when set. The timeout is\n * implemented with an {@link AbortController} so it works across every executor\n * (fetch, Axios, ky, custom) without per-transport support. On expiry the\n * request is aborted with a {@link TimeoutError}.\n */\nfunction executeWithTimeout(\n executor: Executor,\n timeout: number | undefined,\n opts: ExecuteOptions,\n): Promise<unknown> {\n if (timeout == null) return executor.execute(opts);\n\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(new TimeoutError(timeout)),\n timeout,\n );\n const { signal, cleanup } = combineSignals(opts.signal, controller.signal);\n\n return (async () => {\n try {\n return await executor.execute({ ...opts, signal });\n } finally {\n clearTimeout(timer);\n cleanup();\n }\n })();\n}\n\n/**\n * Combines an optional caller signal with the timeout controller's signal into\n * a single signal that aborts when either does. Returns a `cleanup` to detach\n * the listener once the request settles.\n */\nfunction combineSignals(\n caller: AbortSignal | undefined,\n timeoutSignal: AbortSignal,\n): { signal: AbortSignal; cleanup: () => void } {\n if (!caller) return { signal: timeoutSignal, cleanup: () => {} };\n if (caller.aborted) return { signal: caller, cleanup: () => {} };\n\n const controller = new AbortController();\n const onCallerAbort = () => controller.abort(caller.reason);\n const onTimeoutAbort = () => controller.abort(timeoutSignal.reason);\n caller.addEventListener(\"abort\", onCallerAbort, { once: true });\n timeoutSignal.addEventListener(\"abort\", onTimeoutAbort, { once: true });\n return {\n signal: controller.signal,\n cleanup: () => {\n caller.removeEventListener(\"abort\", onCallerAbort);\n timeoutSignal.removeEventListener(\"abort\", onTimeoutAbort);\n },\n };\n}\n","import type {\n CreateExecutorOptions,\n ExecuteOptions,\n Executor,\n ExecutorMiddleware,\n ExecutorPlugin,\n} from \"./types.js\";\n\nfunction pluginToMiddleware(plugin: ExecutorPlugin): ExecutorMiddleware {\n return async (opts, next) => {\n const resolvedOpts = plugin.onRequest ? await plugin.onRequest(opts) : opts;\n try {\n const response = await next(resolvedOpts);\n return plugin.onResponse\n ? await plugin.onResponse(response, resolvedOpts)\n : response;\n } catch (err) {\n if (plugin.onError) {\n await plugin.onError(err, resolvedOpts);\n throw err;\n }\n throw err;\n }\n };\n}\n\nexport function buildChain(\n execute: (options: ExecuteOptions) => Promise<unknown>,\n middlewares: ExecutorMiddleware[],\n): (options: ExecuteOptions) => Promise<unknown> {\n return middlewares.reduceRight<(options: ExecuteOptions) => Promise<unknown>>(\n (next, mw) => (opts) => mw(opts, next),\n execute,\n );\n}\n\n/**\n * Creates an {@link Executor} by wrapping a transport function with plugins.\n *\n * Plugins run in declaration order (first plugin is outermost).\n *\n * For `retry` and `timeout`, use the options on {@link createFetchExecutor}\n * or configure them via the underlying HTTP client (axios, ky).\n *\n * @example\n * ```ts\n * const executor = createExecutor(transport, {\n * plugins: [authPlugin, logger()],\n * });\n * ```\n *\n * @example Unwrap an envelope response\n * ```ts\n * const executor = createExecutor(transport, {\n * unwrap: (raw) => (raw as { data: unknown })?.data ?? raw,\n * });\n * ```\n */\nexport function createExecutor(\n execute: (options: ExecuteOptions) => Promise<unknown>,\n options: CreateExecutorOptions = {},\n): Executor {\n const plugins = [...(options.plugins ?? [])];\n // `unwrap` becomes the innermost `onResponse` hook: appended last so it runs\n // first on the response (immediately after the transport, before user plugins).\n if (options.unwrap) {\n const unwrapFn = options.unwrap;\n plugins.push({ onResponse: (raw) => unwrapFn(raw) });\n }\n const middlewares = plugins.map(pluginToMiddleware);\n return { execute: buildChain(execute, middlewares) };\n}\n\n/**\n * Creates an {@link Executor} that selects the underlying transport at\n * request time based on the result of `resolver`.\n *\n * @example\n * ```ts\n * const apiExecutor = dispatchExecutor(() =>\n * typeof window === 'undefined' ? serverExecutor : clientExecutor,\n * );\n * ```\n */\nexport function dispatchExecutor(\n resolver: (opts: ExecuteOptions) => Executor,\n): Executor {\n return {\n execute: (opts) => resolver(opts).execute(opts),\n };\n}\n","export function serializeParams(\n params: Record<string, unknown>,\n): URLSearchParams {\n const result = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item != null) result.append(key, String(item));\n }\n } else if (typeof value === \"object\") {\n throw new TypeError(\n `serializeParams: value for key \"${key}\" is a plain object. Serialize it to a string before passing as a query parameter.`,\n );\n } else {\n result.append(key, String(value));\n }\n }\n return result;\n}\n","import type { CreateExecutorOptions, ExecuteOptions, Executor, ExecutorMiddleware } from \"./types.js\";\nimport { buildChain, createExecutor } from \"./create-executor.js\";\nimport { withRetry, withTimeout } from \"./middleware.js\";\nimport { serializeParams } from \"./utils/params.js\";\n\nexport type FetchRetryOption =\n | number\n | { count: number; shouldRetry?: (error: unknown, attempt: number) => boolean };\n\nexport interface FetchExecutorOptions extends CreateExecutorOptions {\n defaultHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n retry?: FetchRetryOption;\n timeout?: number;\n}\n\nfunction buildFetchChain(\n transport: (opts: ExecuteOptions) => Promise<unknown>,\n retry?: FetchRetryOption,\n timeout?: number,\n): (opts: ExecuteOptions) => Promise<unknown> {\n const middlewares: ExecutorMiddleware[] = [\n ...(retry != null\n ? [\n typeof retry === \"number\"\n ? withRetry(retry)\n : withRetry(retry.count, { shouldRetry: retry.shouldRetry }),\n ]\n : []),\n ...(timeout != null ? [withTimeout(timeout)] : []),\n ];\n if (middlewares.length === 0) return transport;\n return buildChain(transport, middlewares);\n}\n\n/**\n * Creates an {@link Executor} backed by the browser / Node.js `fetch` API.\n *\n * Suited for SSR environments where you need per-request dynamic headers\n * (e.g. forwarding auth cookies) without sharing state across requests.\n *\n * - Query params are serialized and appended to the URL.\n * - A `Content-Type: application/json` header is added automatically when\n * a request body is present.\n * - Responses with status 204 or `Content-Length: 0` resolve to `null`.\n * - Non-2xx responses throw an {@link HttpError}.\n *\n * @param baseURL - Absolute base URL prepended to every endpoint path.\n * Accepts a static string or a sync/async factory called on every request —\n * useful when the origin depends on runtime environment (e.g. SSR vs CSR).\n * @param options.defaultHeaders - Async factory called on every request to\n * produce headers (e.g. reading cookies in a Next.js server component).\n * @param options.plugins - Plugins applied around the fetch call. Each retry\n * attempt re-runs `onRequest` hooks (so headers are refreshed per attempt)\n * and `onError` hooks. Token-refresh-on-401 patterns work by updating an\n * external token store in `onError` and letting `onRequest` pick it up on\n * the next attempt.\n * @param options.retry - Number of retries, or `{ count, shouldRetry? }`.\n * @param options.timeout - Per-attempt timeout in milliseconds.\n *\n * @example Minimal — no options needed\n * ```ts\n * const executor = createFetchExecutor('https://api.example.com');\n * ```\n *\n * @example Dynamic base URL for SSR/CSR\n * ```ts\n * const executor = createFetchExecutor(\n * () => typeof window === 'undefined' ? 'http://localhost:3000/api' : '/api',\n * );\n * ```\n *\n * @example SSR with bearer token\n * ```ts\n * const executor = createFetchExecutor('https://api.example.com', {\n * defaultHeaders: async () => {\n * const token = await getServerToken();\n * return token ? { Authorization: `Bearer ${token}` } : {};\n * },\n * });\n * ```\n *\n * @example With retry and timeout\n * ```ts\n * const executor = createFetchExecutor('https://api.example.com', {\n * retry: 2,\n * timeout: 8_000,\n * });\n * ```\n */\nexport function createFetchExecutor(\n baseURL: string | (() => string | Promise<string>),\n options?: FetchExecutorOptions,\n): Executor {\n const transport = async ({\n method,\n url,\n params,\n body,\n headers,\n signal,\n }: ExecuteOptions) => {\n const resolvedBase = typeof baseURL === \"function\" ? await baseURL() : baseURL;\n const fullURL = new URL(resolvedBase.replace(/\\/$/, \"\") + url);\n if (params) {\n serializeParams(params).forEach((v, k) => {\n fullURL.searchParams.set(k, v);\n });\n }\n\n const defaultHeaders = (await options?.defaultHeaders?.()) ?? {};\n\n const res = await fetch(fullURL.toString(), {\n method,\n headers: {\n ...defaultHeaders,\n ...headers,\n ...(body != null ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: body != null ? JSON.stringify(body) : undefined,\n signal,\n });\n\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null);\n throw new HttpError(res.status, res.statusText, errorBody, { url: fullURL.toString(), method });\n }\n if (res.status === 204 || res.status === 205 || res.status === 304) {\n return null;\n }\n const text = await res.text();\n return text === \"\" ? null : JSON.parse(text);\n };\n\n const executor = createExecutor(transport, { plugins: options?.plugins, unwrap: options?.unwrap });\n return { execute: buildFetchChain(executor.execute, options?.retry, options?.timeout) };\n}\n\n/**\n * Thrown by {@link createFetchExecutor} when the server returns a non-2xx\n * status code. The Axios and ky executors also normalize their transport\n * errors to `HttpError`, so `onError` plugins and callers can branch on a\n * single error type regardless of the underlying transport. The original\n * transport error (e.g. an `AxiosError` or ky `HTTPError`) is preserved on\n * the `cause` property.\n *\n * @example\n * ```ts\n * try {\n * await api.getDetail({ path: { id: 999 } });\n * } catch (err) {\n * if (err instanceof HttpError && err.status === 404) {\n * // handle not-found\n * }\n * }\n * ```\n */\nexport class HttpError extends Error {\n public readonly url?: string;\n public readonly method?: string;\n\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body: unknown = null,\n options?: { url?: string; method?: string; cause?: unknown },\n ) {\n super(`HTTP ${status}: ${statusText}`, { cause: options?.cause });\n this.name = \"HttpError\";\n this.url = options?.url;\n this.method = options?.method;\n }\n}\n","import type { StandardSchemaV1 } from \"../standard-schema.js\";\nimport type { AnyValidator, RequestShape, Validator } from \"../types.js\";\n\n/** The per-bucket validators of the SE-12 separated endpoint form. */\nexport interface RequestBuckets {\n path?: AnyValidator;\n query?: AnyValidator;\n body?: AnyValidator;\n}\n\n/**\n * The synthesized envelope request validator produced from separated buckets.\n * Carries a Zod-like `shape` (`{ path?, query?, body? }`) so `@routar/react-query`'s\n * flatten support can still introspect each bucket's keys via duck-typing — the\n * bucket form is indistinguishable from the envelope form downstream.\n */\nexport type ComposedRequest = AnyValidator<RequestShape> & {\n shape: Record<string, AnyValidator>;\n};\n\nconst BUCKET_KEYS = [\"path\", \"query\", \"body\"] as const;\n\nfunction hasParse(v: AnyValidator): v is Validator<unknown> {\n return typeof (v as Validator<unknown>).parse === \"function\";\n}\n\n/** Validates one bucket, returning a Standard-Schema-style result. */\nasync function validateBucket(\n validator: AnyValidator,\n value: unknown,\n): Promise<StandardSchemaV1.Result<unknown>> {\n if (hasParse(validator)) {\n try {\n return { value: validator.parse(value) };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { issues: [{ message }] };\n }\n }\n return (validator as StandardSchemaV1)[\"~standard\"].validate(value);\n}\n\n/**\n * Composes separated `{ pathParams, query, body }` validators into a single\n * envelope request validator equivalent to\n * `z.object({ path, query, body })` — the shape the rest of the pipeline\n * (createApi, react-query) already understands.\n *\n * When every bucket exposes `.parse`, a synchronous `.parse` is provided so the\n * common (Zod/Valibot/Yup) path keeps its native error richness. Otherwise a\n * Standard Schema `~standard` validator is provided, aggregating each bucket's\n * issues under its bucket key.\n */\nexport function composeRequest(buckets: RequestBuckets): ComposedRequest {\n const entries = BUCKET_KEYS.filter(\n (k) => buckets[k] !== undefined,\n ).map((k) => [k, buckets[k] as AnyValidator] as const);\n\n const shape: Record<string, AnyValidator> = {};\n for (const [k, v] of entries) shape[k] = v;\n\n const standard: StandardSchemaV1<unknown, RequestShape> = {\n \"~standard\": {\n version: 1,\n vendor: \"routar\",\n validate: async (data: unknown) => {\n const input = (data ?? {}) as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n const issues: StandardSchemaV1.Issue[] = [];\n for (const [k, v] of entries) {\n const result = await validateBucket(v, input[k]);\n if (result.issues) {\n for (const issue of result.issues) {\n issues.push({ message: issue.message, path: [k, ...(issue.path ?? [])] });\n }\n } else {\n out[k] = result.value;\n }\n }\n return issues.length > 0\n ? { issues }\n : { value: out as RequestShape };\n },\n },\n };\n\n if (entries.every(([, v]) => hasParse(v))) {\n const parse = (data: unknown): RequestShape => {\n const input = (data ?? {}) as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const [k, v] of entries) {\n out[k] = (v as Validator<unknown>).parse(input[k]);\n }\n return out as RequestShape;\n };\n return Object.assign(standard, { parse, shape });\n }\n\n return Object.assign(standard, { shape });\n}\n","import type {\n AnyValidator,\n HttpMethod,\n RequestShape,\n Validator,\n ValidatorOutput,\n} from \"./types.js\";\nimport { composeRequest } from \"./utils/compose-request.js\";\n\n/**\n * Extracts `:param` segment names from a path template string as a union of\n * string literals.\n *\n * @example\n * ```ts\n * type P = PathParams<'/:userId/posts/:postId'>; // 'userId' | 'postId'\n * ```\n */\nexport type PathParams<TPath extends string> =\n TPath extends `${string}:${infer Param}/${infer Rest}`\n ? Param | PathParams<Rest>\n : TPath extends `${string}:${infer Param}`\n ? Param\n : never;\n\n/**\n * When `TPath` contains dynamic segments (`:param`), requires `request.path`\n * to include all extracted param names. No constraint for static paths.\n */\ntype PathConstraint<TPath extends string> = [PathParams<TPath>] extends [never]\n ? {}\n : { path: Record<PathParams<TPath>, unknown> };\n\n/**\n * Builds the envelope request output type from the SE-12 separated bucket\n * validators. Each bucket contributes its key only when supplied — the tuple\n * wrapping (`[T] extends [never]`) prevents the `never` default from\n * distributing and collapsing the whole intersection.\n */\ntype BucketRequestOutput<TPathParams, TQuery, TBody> = ([\n TPathParams,\n] extends [never]\n ? {}\n : { path: ValidatorOutput<TPathParams> }) &\n ([TQuery] extends [never] ? {} : { query: ValidatorOutput<TQuery> }) &\n ([TBody] extends [never] ? {} : { body: ValidatorOutput<TBody> });\n\n/**\n * The `pathParams` field of the separated form: required when `TPath` has\n * dynamic segments, optional otherwise. The validator type is further\n * constrained (at the generic level) to cover every `:param` name.\n */\ntype BucketPathField<TPath extends string, TPathParams> = [\n PathParams<TPath>,\n] extends [never]\n ? { pathParams?: TPathParams }\n : { pathParams: TPathParams };\n\n/**\n * Type-safe endpoint definition helper.\n *\n * Use this instead of a plain object literal to get full type inference on\n * `adapter` without requiring explicit annotations or `as any` casts.\n * The four overloads cover every combination of optional `request` validator\n * and optional `adapter` function while keeping all return-type fields\n * required so that TypeScript can narrow them downstream.\n *\n * When `path` contains dynamic segments (e.g. `'/:id'`), TypeScript enforces\n * that `request` includes a matching `path` field with those param names.\n * A mismatch or missing key is a compile-time error.\n *\n * The literal HTTP method (`'GET'`, `'POST'`, …) is preserved on the return\n * type — `endpoint({ method: 'GET', ... }).method` is typed `'GET'`, not the\n * `HttpMethod` union.\n *\n * @example Basic GET with no params\n * ```ts\n * const getList = endpoint({ method: 'GET', path: '/', response: z.array(TodoSchema) });\n * ```\n *\n * @example GET with query params\n * ```ts\n * const search = endpoint({\n * method: 'GET',\n * path: '/search',\n * request: z.object({ query: z.object({ q: z.string(), limit: z.number().optional() }) }),\n * response: z.array(TodoSchema),\n * });\n * ```\n *\n * @example POST with body\n * ```ts\n * const create = endpoint({\n * method: 'POST',\n * path: '/',\n * request: z.object({ body: z.object({ title: z.string() }) }),\n * response: TodoSchema,\n * });\n * ```\n *\n * @example Adapter — raw is inferred from the response schema, no cast needed\n * ```ts\n * const getDetail = endpoint({\n * method: 'GET',\n * path: '/:id',\n * request: z.object({ path: z.object({ id: z.number() }) }),\n * response: TodoRawSchema,\n * adapter: (raw) => ({ ...raw, label: `#${raw.id} ${raw.title}` }),\n * });\n * ```\n *\n * @example Path param enforcement\n * ```ts\n * // ✅ path has ':id' → request.path.id is required\n * const getDetail = endpoint({\n * method: 'GET',\n * path: '/:id',\n * request: z.object({ path: z.object({ id: z.number() }) }),\n * response: TodoSchema,\n * });\n *\n * // ❌ compile error — 'id' is missing from request.path\n * const broken = endpoint({\n * method: 'GET',\n * path: '/:id',\n * request: z.object({ query: z.object({ foo: z.string() }) }),\n * response: TodoSchema,\n * });\n * ```\n */\n// request O + adapter O\nexport function endpoint<\n TMethod extends HttpMethod,\n TPath extends string,\n TReq extends AnyValidator<RequestShape & PathConstraint<TPath>>,\n TResponse extends AnyValidator,\n TOut,\n>(spec: {\n method: TMethod;\n path: TPath;\n request: TReq;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n}): {\n method: TMethod;\n path: string;\n request: TReq;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n};\n\n// request O + adapter X\nexport function endpoint<\n TMethod extends HttpMethod,\n TPath extends string,\n TReq extends AnyValidator<RequestShape & PathConstraint<TPath>>,\n TResponse extends AnyValidator,\n>(spec: {\n method: TMethod;\n path: TPath;\n request: TReq;\n response: TResponse;\n}): {\n method: TMethod;\n path: string;\n request: TReq;\n response: TResponse;\n};\n\n// request X + adapter O\nexport function endpoint<\n TMethod extends HttpMethod,\n TResponse extends AnyValidator,\n TOut,\n>(spec: {\n method: TMethod;\n path: string;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n}): {\n method: TMethod;\n path: string;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n};\n\n// request X + adapter X\nexport function endpoint<\n TMethod extends HttpMethod,\n TResponse extends AnyValidator,\n>(spec: {\n method: TMethod;\n path: string;\n response: TResponse;\n}): {\n method: TMethod;\n path: string;\n response: TResponse;\n};\n\n// ─────────────────────────────────────────────────────────────────────────\n// SE-12 — separated request buckets (additive; the envelope `request` form\n// above keeps working unchanged). Declare each part on its own field instead of\n// wrapping a `z.object({ path, query, body })` envelope:\n//\n// endpoint({\n// method: 'GET', path: '/:id',\n// pathParams: z.object({ id: z.number() }),\n// query: z.object({ q: z.string() }),\n// response: TodoSchema,\n// })\n//\n// Internally the buckets are composed into the same envelope request validator,\n// so call sites, keys, react-query flatten, and MSW all behave identically.\n// ─────────────────────────────────────────────────────────────────────────\n\n// separated buckets + adapter O\nexport function endpoint<\n TMethod extends HttpMethod,\n TPath extends string,\n TResponse extends AnyValidator,\n TOut,\n TPathParams extends AnyValidator<Record<PathParams<TPath>, unknown>> = never,\n TQuery extends AnyValidator = never,\n TBody extends AnyValidator = never,\n>(\n spec: {\n method: TMethod;\n path: TPath;\n query?: TQuery;\n body?: TBody;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n } & BucketPathField<TPath, TPathParams>,\n): {\n method: TMethod;\n path: string;\n request: Validator<BucketRequestOutput<TPathParams, TQuery, TBody>>;\n response: TResponse;\n adapter: (raw: ValidatorOutput<TResponse>) => TOut;\n};\n\n// separated buckets + adapter X\nexport function endpoint<\n TMethod extends HttpMethod,\n TPath extends string,\n TResponse extends AnyValidator,\n TPathParams extends AnyValidator<Record<PathParams<TPath>, unknown>> = never,\n TQuery extends AnyValidator = never,\n TBody extends AnyValidator = never,\n>(\n spec: {\n method: TMethod;\n path: TPath;\n query?: TQuery;\n body?: TBody;\n response: TResponse;\n } & BucketPathField<TPath, TPathParams>,\n): {\n method: TMethod;\n path: string;\n request: Validator<BucketRequestOutput<TPathParams, TQuery, TBody>>;\n response: TResponse;\n};\n\nexport function endpoint(spec: Record<string, unknown>): unknown {\n // Separated-bucket form (SE-12): normalize into the envelope `request`.\n if (\n spec.request === undefined &&\n (spec.pathParams !== undefined ||\n spec.query !== undefined ||\n spec.body !== undefined)\n ) {\n const { pathParams, query, body, ...rest } = spec;\n return {\n ...rest,\n request: composeRequest({\n path: pathParams as AnyValidator | undefined,\n query: query as AnyValidator | undefined,\n body: body as AnyValidator | undefined,\n }),\n };\n }\n return spec;\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Standard Schema interface (v1), vendored to keep `@routar/core`
|
|
3
|
+
* dependency-free. Standard Schema is the common validation interface adopted
|
|
4
|
+
* by Zod 3.24+, Valibot, ArkType, and others — exposed via the `~standard`
|
|
5
|
+
* property. The spec is type-only (no runtime), and the authors explicitly
|
|
6
|
+
* permit copying the interface rather than depending on `@standard-schema/spec`.
|
|
7
|
+
*
|
|
8
|
+
* @see https://standardschema.dev
|
|
9
|
+
*/
|
|
10
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
11
|
+
/** The Standard Schema properties. */
|
|
12
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
13
|
+
}
|
|
14
|
+
declare namespace StandardSchemaV1 {
|
|
15
|
+
/** The Standard Schema properties interface. */
|
|
16
|
+
interface Props<Input = unknown, Output = Input> {
|
|
17
|
+
/** The version number of the standard. */
|
|
18
|
+
readonly version: 1;
|
|
19
|
+
/** The vendor name of the schema library. */
|
|
20
|
+
readonly vendor: string;
|
|
21
|
+
/** Validates unknown input values. */
|
|
22
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
23
|
+
/** Inferred types associated with the schema. */
|
|
24
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** The result interface of the validate function. */
|
|
27
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
28
|
+
/** The result interface if validation succeeds. */
|
|
29
|
+
interface SuccessResult<Output> {
|
|
30
|
+
/** The typed output value. */
|
|
31
|
+
readonly value: Output;
|
|
32
|
+
/** The non-existent issues. */
|
|
33
|
+
readonly issues?: undefined;
|
|
34
|
+
}
|
|
35
|
+
/** The result interface if validation fails. */
|
|
36
|
+
interface FailureResult {
|
|
37
|
+
/** The issues of failed validation. */
|
|
38
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
39
|
+
}
|
|
40
|
+
/** The issue interface of the failure output. */
|
|
41
|
+
interface Issue {
|
|
42
|
+
/** The error message of the issue. */
|
|
43
|
+
readonly message: string;
|
|
44
|
+
/** The path of the issue, if any. */
|
|
45
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
46
|
+
}
|
|
47
|
+
/** The path segment interface of the issue. */
|
|
48
|
+
interface PathSegment {
|
|
49
|
+
/** The key representing a path segment. */
|
|
50
|
+
readonly key: PropertyKey;
|
|
51
|
+
}
|
|
52
|
+
/** The Standard Schema types interface. */
|
|
53
|
+
interface Types<Input = unknown, Output = Input> {
|
|
54
|
+
/** The input type of the schema. */
|
|
55
|
+
readonly input: Input;
|
|
56
|
+
/** The output type of the schema. */
|
|
57
|
+
readonly output: Output;
|
|
58
|
+
}
|
|
59
|
+
/** Infers the input type of a Standard Schema. */
|
|
60
|
+
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
61
|
+
/** Infers the output type of a Standard Schema. */
|
|
62
|
+
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare class ValidationError extends Error {
|
|
66
|
+
readonly cause?: unknown;
|
|
67
|
+
constructor(message: string, cause?: unknown);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown internally when a Standard Schema validator (`~standard.validate`)
|
|
71
|
+
* reports issues. It is wrapped as the `cause` of a {@link ValidationError} by
|
|
72
|
+
* the API client, mirroring how a thrown `.parse()` error becomes the cause —
|
|
73
|
+
* so callers branch on `ValidationError` regardless of the validator library.
|
|
74
|
+
* The structured `issues` array is preserved for drift reporting.
|
|
75
|
+
*/
|
|
76
|
+
declare class StandardSchemaError extends Error {
|
|
77
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
78
|
+
constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
79
|
+
}
|
|
80
|
+
|
|
1
81
|
type HttpMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
|
2
82
|
/** Options passed to {@link Executor.execute} on every HTTP call. */
|
|
3
83
|
interface ExecuteOptions {
|
|
@@ -13,6 +93,39 @@ interface ExecuteOptions {
|
|
|
13
93
|
headers?: Record<string, string>;
|
|
14
94
|
signal?: AbortSignal;
|
|
15
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Per-call transport options, passed as the optional second argument to any
|
|
98
|
+
* generated endpoint function: `api.create(params, { signal, headers, timeout })`.
|
|
99
|
+
*
|
|
100
|
+
* Passing a bare {@link AbortSignal} as the second argument is still supported
|
|
101
|
+
* (backward compatible) — it is treated as `{ signal }`.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* await api.create({ body }, {
|
|
106
|
+
* signal: controller.signal,
|
|
107
|
+
* headers: { 'Idempotency-Key': key },
|
|
108
|
+
* timeout: 30_000,
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
interface EndpointCallOptions {
|
|
113
|
+
/** Aborts the request when the signal fires. */
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
/**
|
|
116
|
+
* Per-call headers. Seeded onto the request before plugin `onRequest` hooks
|
|
117
|
+
* run, and merged over the executor's default headers — so per-call headers
|
|
118
|
+
* win over defaults. A plugin that sets the same header key still wins on
|
|
119
|
+
* collision (plugins are cross-cutting policy such as auth).
|
|
120
|
+
*/
|
|
121
|
+
headers?: Record<string, string>;
|
|
122
|
+
/**
|
|
123
|
+
* Per-call timeout in milliseconds. Aborts the request with a `TimeoutError`
|
|
124
|
+
* when exceeded. Applied by the core client (transport-agnostic) and composes
|
|
125
|
+
* with any executor-level timeout (whichever fires first wins).
|
|
126
|
+
*/
|
|
127
|
+
timeout?: number;
|
|
128
|
+
}
|
|
16
129
|
/**
|
|
17
130
|
* Transport abstraction. Implement this to support any HTTP client.
|
|
18
131
|
*
|
|
@@ -100,8 +213,24 @@ interface CreateExecutorOptions {
|
|
|
100
213
|
interface Validator<TOutput> {
|
|
101
214
|
parse(data: unknown): TOutput;
|
|
102
215
|
}
|
|
103
|
-
/**
|
|
104
|
-
|
|
216
|
+
/**
|
|
217
|
+
* A validator accepted by routar: either the `.parse()` duck-typed
|
|
218
|
+
* {@link Validator} (Zod, Valibot, Yup, or any object with `.parse()`) **or** a
|
|
219
|
+
* [Standard Schema](https://standardschema.dev) (`~standard` — Zod 3.24+,
|
|
220
|
+
* Valibot, ArkType, …). Both forms are validated at runtime; the `.parse()` path
|
|
221
|
+
* is preferred when present (synchronous), otherwise `~standard.validate` is
|
|
222
|
+
* used (sync or async).
|
|
223
|
+
*
|
|
224
|
+
* @template TOutput Parsed/validated output type.
|
|
225
|
+
*/
|
|
226
|
+
type AnyValidator<TOutput = unknown> = Validator<TOutput> | StandardSchemaV1<unknown, TOutput>;
|
|
227
|
+
/**
|
|
228
|
+
* Extracts the output type of an {@link AnyValidator} — the `.parse()` return
|
|
229
|
+
* type for a {@link Validator}, or the Standard Schema output type. The
|
|
230
|
+
* `Validator` branch is checked first so a schema satisfying both (e.g. Zod)
|
|
231
|
+
* resolves through its `.parse()` signature.
|
|
232
|
+
*/
|
|
233
|
+
type ValidatorOutput<T> = T extends Validator<infer O> ? O : T extends StandardSchemaV1<unknown, infer O> ? O : never;
|
|
105
234
|
/**
|
|
106
235
|
* Shape of an endpoint's request parameters.
|
|
107
236
|
*
|
|
@@ -123,11 +252,11 @@ interface RequestShape {
|
|
|
123
252
|
* @template TResponse Validator for the raw response.
|
|
124
253
|
* @template TAdapter Optional adapter function type.
|
|
125
254
|
*/
|
|
126
|
-
interface EndpointSpec<TRequest extends RequestShape = RequestShape, TResponse extends
|
|
255
|
+
interface EndpointSpec<TRequest extends RequestShape = RequestShape, TResponse extends AnyValidator = AnyValidator, TAdapter extends ((raw: ValidatorOutput<TResponse>) => unknown) | undefined = undefined> {
|
|
127
256
|
method: HttpMethod;
|
|
128
257
|
path: string;
|
|
129
258
|
/** Validates and narrows request parameters before the HTTP call. */
|
|
130
|
-
request?:
|
|
259
|
+
request?: AnyValidator<TRequest>;
|
|
131
260
|
/** Validates the raw server response. */
|
|
132
261
|
response: TResponse;
|
|
133
262
|
/**
|
|
@@ -176,6 +305,31 @@ type ApiTypes<TApi> = {
|
|
|
176
305
|
response: R;
|
|
177
306
|
} : TApi[K] extends object ? ApiTypes<TApi[K]> : never;
|
|
178
307
|
};
|
|
308
|
+
/**
|
|
309
|
+
* Per-kind validation mode.
|
|
310
|
+
*
|
|
311
|
+
* - `true` (default) — validate and throw {@link ValidationError} on failure.
|
|
312
|
+
* - `false` — skip validation; raw data passes through untouched.
|
|
313
|
+
* - `'warn'` — attempt validation but, on failure, pass the raw data through
|
|
314
|
+
* and report the error via {@link CreateApiOptions.onValidationError} instead
|
|
315
|
+
* of throwing. The drift-observation mode: surface schema drift without
|
|
316
|
+
* turning it into an outage.
|
|
317
|
+
*/
|
|
318
|
+
type ValidationMode = boolean | "warn";
|
|
319
|
+
/**
|
|
320
|
+
* Context passed to {@link CreateApiOptions.onValidationError} describing which
|
|
321
|
+
* call and which phase produced the validation failure.
|
|
322
|
+
*/
|
|
323
|
+
interface ValidationErrorContext {
|
|
324
|
+
/** Which phase failed — request params or the server response. */
|
|
325
|
+
kind: "request" | "response";
|
|
326
|
+
/** The endpoint's HTTP method. */
|
|
327
|
+
method: HttpMethod;
|
|
328
|
+
/** The resolved request URL (path is already substituted). */
|
|
329
|
+
url: string;
|
|
330
|
+
/** The raw data that failed validation (raw params or raw response). */
|
|
331
|
+
data: unknown;
|
|
332
|
+
}
|
|
179
333
|
/**
|
|
180
334
|
* Options for {@link createApi}.
|
|
181
335
|
*
|
|
@@ -186,6 +340,12 @@ type ApiTypes<TApi> = {
|
|
|
186
340
|
*
|
|
187
341
|
* // Keep request validation (catch call-site bugs), skip response in prod
|
|
188
342
|
* createApi(executor, router, { validate: { request: true, response: false } });
|
|
343
|
+
*
|
|
344
|
+
* // Observe response drift without breaking production
|
|
345
|
+
* createApi(executor, router, {
|
|
346
|
+
* validate: { request: true, response: 'warn' },
|
|
347
|
+
* onValidationError: (err, ctx) => Sentry.captureException(err, { extra: ctx }),
|
|
348
|
+
* });
|
|
189
349
|
* ```
|
|
190
350
|
*/
|
|
191
351
|
interface CreateApiOptions {
|
|
@@ -194,18 +354,25 @@ interface CreateApiOptions {
|
|
|
194
354
|
*
|
|
195
355
|
* - `true` (default) — validate both request and response.
|
|
196
356
|
* - `false` — skip both; raw params and raw response pass through.
|
|
197
|
-
* - `
|
|
357
|
+
* - `'warn'` — validate both, but pass raw data through and report via
|
|
358
|
+
* {@link CreateApiOptions.onValidationError} instead of throwing.
|
|
359
|
+
* - `{ request?, response? }` — set each independently (each a
|
|
360
|
+
* {@link ValidationMode}).
|
|
198
361
|
*/
|
|
199
|
-
validate?:
|
|
200
|
-
request?:
|
|
201
|
-
response?:
|
|
362
|
+
validate?: ValidationMode | {
|
|
363
|
+
request?: ValidationMode;
|
|
364
|
+
response?: ValidationMode;
|
|
202
365
|
};
|
|
366
|
+
/**
|
|
367
|
+
* Called when validation fails under `'warn'` mode (instead of throwing).
|
|
368
|
+
* Use it to report schema drift to your observability stack. Never called
|
|
369
|
+
* when `validate` is `true` (which throws) or `false` (which skips).
|
|
370
|
+
*/
|
|
371
|
+
onValidationError?: (error: ValidationError, context: ValidationErrorContext) => void;
|
|
203
372
|
}
|
|
204
373
|
|
|
205
374
|
/** Callable type for a single endpoint on the generated API client. */
|
|
206
|
-
type EndpointFn<TSpec extends EndpointSpec<any, any, any>> = TSpec["request"] extends
|
|
207
|
-
parse: (data: unknown) => infer R;
|
|
208
|
-
} ? (params: R, signal?: AbortSignal) => Promise<InferResponse<TSpec>> : (params?: RequestShape, signal?: AbortSignal) => Promise<InferResponse<TSpec>>;
|
|
375
|
+
type EndpointFn<TSpec extends EndpointSpec<any, any, any>> = TSpec["request"] extends Validator<infer R> ? (params: R, options?: AbortSignal | EndpointCallOptions) => Promise<InferResponse<TSpec>> : TSpec["request"] extends StandardSchemaV1<unknown, infer O> ? (params: O, options?: AbortSignal | EndpointCallOptions) => Promise<InferResponse<TSpec>> : (params?: RequestShape, options?: AbortSignal | EndpointCallOptions) => Promise<InferResponse<TSpec>>;
|
|
209
376
|
/**
|
|
210
377
|
* Fully-typed API client produced by {@link createApi}.
|
|
211
378
|
* Nested {@link RouterDef} entries become nested sub-client objects.
|
|
@@ -444,6 +611,33 @@ type PathParams<TPath extends string> = TPath extends `${string}:${infer Param}/
|
|
|
444
611
|
type PathConstraint<TPath extends string> = [PathParams<TPath>] extends [never] ? {} : {
|
|
445
612
|
path: Record<PathParams<TPath>, unknown>;
|
|
446
613
|
};
|
|
614
|
+
/**
|
|
615
|
+
* Builds the envelope request output type from the SE-12 separated bucket
|
|
616
|
+
* validators. Each bucket contributes its key only when supplied — the tuple
|
|
617
|
+
* wrapping (`[T] extends [never]`) prevents the `never` default from
|
|
618
|
+
* distributing and collapsing the whole intersection.
|
|
619
|
+
*/
|
|
620
|
+
type BucketRequestOutput<TPathParams, TQuery, TBody> = ([
|
|
621
|
+
TPathParams
|
|
622
|
+
] extends [never] ? {} : {
|
|
623
|
+
path: ValidatorOutput<TPathParams>;
|
|
624
|
+
}) & ([TQuery] extends [never] ? {} : {
|
|
625
|
+
query: ValidatorOutput<TQuery>;
|
|
626
|
+
}) & ([TBody] extends [never] ? {} : {
|
|
627
|
+
body: ValidatorOutput<TBody>;
|
|
628
|
+
});
|
|
629
|
+
/**
|
|
630
|
+
* The `pathParams` field of the separated form: required when `TPath` has
|
|
631
|
+
* dynamic segments, optional otherwise. The validator type is further
|
|
632
|
+
* constrained (at the generic level) to cover every `:param` name.
|
|
633
|
+
*/
|
|
634
|
+
type BucketPathField<TPath extends string, TPathParams> = [
|
|
635
|
+
PathParams<TPath>
|
|
636
|
+
] extends [never] ? {
|
|
637
|
+
pathParams?: TPathParams;
|
|
638
|
+
} : {
|
|
639
|
+
pathParams: TPathParams;
|
|
640
|
+
};
|
|
447
641
|
/**
|
|
448
642
|
* Type-safe endpoint definition helper.
|
|
449
643
|
*
|
|
@@ -516,31 +710,31 @@ type PathConstraint<TPath extends string> = [PathParams<TPath>] extends [never]
|
|
|
516
710
|
* });
|
|
517
711
|
* ```
|
|
518
712
|
*/
|
|
519
|
-
declare function endpoint<TMethod extends HttpMethod, TPath extends string,
|
|
713
|
+
declare function endpoint<TMethod extends HttpMethod, TPath extends string, TReq extends AnyValidator<RequestShape & PathConstraint<TPath>>, TResponse extends AnyValidator, TOut>(spec: {
|
|
520
714
|
method: TMethod;
|
|
521
715
|
path: TPath;
|
|
522
|
-
request:
|
|
716
|
+
request: TReq;
|
|
523
717
|
response: TResponse;
|
|
524
718
|
adapter: (raw: ValidatorOutput<TResponse>) => TOut;
|
|
525
719
|
}): {
|
|
526
720
|
method: TMethod;
|
|
527
721
|
path: string;
|
|
528
|
-
request:
|
|
722
|
+
request: TReq;
|
|
529
723
|
response: TResponse;
|
|
530
724
|
adapter: (raw: ValidatorOutput<TResponse>) => TOut;
|
|
531
725
|
};
|
|
532
|
-
declare function endpoint<TMethod extends HttpMethod, TPath extends string,
|
|
726
|
+
declare function endpoint<TMethod extends HttpMethod, TPath extends string, TReq extends AnyValidator<RequestShape & PathConstraint<TPath>>, TResponse extends AnyValidator>(spec: {
|
|
533
727
|
method: TMethod;
|
|
534
728
|
path: TPath;
|
|
535
|
-
request:
|
|
729
|
+
request: TReq;
|
|
536
730
|
response: TResponse;
|
|
537
731
|
}): {
|
|
538
732
|
method: TMethod;
|
|
539
733
|
path: string;
|
|
540
|
-
request:
|
|
734
|
+
request: TReq;
|
|
541
735
|
response: TResponse;
|
|
542
736
|
};
|
|
543
|
-
declare function endpoint<TMethod extends HttpMethod, TResponse extends
|
|
737
|
+
declare function endpoint<TMethod extends HttpMethod, TResponse extends AnyValidator, TOut>(spec: {
|
|
544
738
|
method: TMethod;
|
|
545
739
|
path: string;
|
|
546
740
|
response: TResponse;
|
|
@@ -551,7 +745,7 @@ declare function endpoint<TMethod extends HttpMethod, TResponse extends Validato
|
|
|
551
745
|
response: TResponse;
|
|
552
746
|
adapter: (raw: ValidatorOutput<TResponse>) => TOut;
|
|
553
747
|
};
|
|
554
|
-
declare function endpoint<TMethod extends HttpMethod, TResponse extends
|
|
748
|
+
declare function endpoint<TMethod extends HttpMethod, TResponse extends AnyValidator>(spec: {
|
|
555
749
|
method: TMethod;
|
|
556
750
|
path: string;
|
|
557
751
|
response: TResponse;
|
|
@@ -560,6 +754,32 @@ declare function endpoint<TMethod extends HttpMethod, TResponse extends Validato
|
|
|
560
754
|
path: string;
|
|
561
755
|
response: TResponse;
|
|
562
756
|
};
|
|
757
|
+
declare function endpoint<TMethod extends HttpMethod, TPath extends string, TResponse extends AnyValidator, TOut, TPathParams extends AnyValidator<Record<PathParams<TPath>, unknown>> = never, TQuery extends AnyValidator = never, TBody extends AnyValidator = never>(spec: {
|
|
758
|
+
method: TMethod;
|
|
759
|
+
path: TPath;
|
|
760
|
+
query?: TQuery;
|
|
761
|
+
body?: TBody;
|
|
762
|
+
response: TResponse;
|
|
763
|
+
adapter: (raw: ValidatorOutput<TResponse>) => TOut;
|
|
764
|
+
} & BucketPathField<TPath, TPathParams>): {
|
|
765
|
+
method: TMethod;
|
|
766
|
+
path: string;
|
|
767
|
+
request: Validator<BucketRequestOutput<TPathParams, TQuery, TBody>>;
|
|
768
|
+
response: TResponse;
|
|
769
|
+
adapter: (raw: ValidatorOutput<TResponse>) => TOut;
|
|
770
|
+
};
|
|
771
|
+
declare function endpoint<TMethod extends HttpMethod, TPath extends string, TResponse extends AnyValidator, TPathParams extends AnyValidator<Record<PathParams<TPath>, unknown>> = never, TQuery extends AnyValidator = never, TBody extends AnyValidator = never>(spec: {
|
|
772
|
+
method: TMethod;
|
|
773
|
+
path: TPath;
|
|
774
|
+
query?: TQuery;
|
|
775
|
+
body?: TBody;
|
|
776
|
+
response: TResponse;
|
|
777
|
+
} & BucketPathField<TPath, TPathParams>): {
|
|
778
|
+
method: TMethod;
|
|
779
|
+
path: string;
|
|
780
|
+
request: Validator<BucketRequestOutput<TPathParams, TQuery, TBody>>;
|
|
781
|
+
response: TResponse;
|
|
782
|
+
};
|
|
563
783
|
|
|
564
784
|
/**
|
|
565
785
|
* Groups a set of endpoint specs (and optional nested routers) under a shared
|
|
@@ -647,9 +867,17 @@ declare function serializeParams(params: Record<string, unknown>): URLSearchPara
|
|
|
647
867
|
declare function joinPaths(...segments: string[]): string;
|
|
648
868
|
declare function resolvePath(pathTemplate: string, params?: Record<string, unknown>): string;
|
|
649
869
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
870
|
+
/**
|
|
871
|
+
* Validates `data` with an {@link AnyValidator}, returning the parsed value.
|
|
872
|
+
*
|
|
873
|
+
* Prefers the synchronous `.parse()` path when present (Zod, Valibot, Yup, or
|
|
874
|
+
* any object with `.parse()`); a thrown parse error propagates unchanged.
|
|
875
|
+
* Otherwise uses the Standard Schema `~standard.validate` path (sync or async);
|
|
876
|
+
* reported issues are thrown as a {@link StandardSchemaError}.
|
|
877
|
+
*
|
|
878
|
+
* Exported for executor / mock-handler authors who need routar's exact
|
|
879
|
+
* validate-or-throw semantics for both validator styles.
|
|
880
|
+
*/
|
|
881
|
+
declare function runValidator(validator: AnyValidator, data: unknown): Promise<unknown>;
|
|
654
882
|
|
|
655
|
-
export { type ApiClient, type ApiClientWithRouter, type ApiTypes, type CreateApiOptions, type CreateExecutorOptions, type EndpointSpec, type ExecuteOptions, type Executor, type ExecutorPlugin, type FetchExecutorOptions, type FetchRetryOption, HttpError, type HttpMethod, type InferResponse, type PathParams, type RequestShape, type RouterDef, type RouterEndpoints, type RouterEntry, TimeoutError, ValidationError, type Validator, type ValidatorOutput, createApi, createExecutor, createFetchExecutor, definePlugin, defineRouter, dispatchExecutor, endpoint, isRouterDef, joinPaths, logger, resolvePath, serializeParams };
|
|
883
|
+
export { type AnyValidator, type ApiClient, type ApiClientWithRouter, type ApiTypes, type CreateApiOptions, type CreateExecutorOptions, type EndpointCallOptions, type EndpointSpec, type ExecuteOptions, type Executor, type ExecutorPlugin, type FetchExecutorOptions, type FetchRetryOption, HttpError, type HttpMethod, type InferResponse, type PathParams, type RequestShape, type RouterDef, type RouterEndpoints, type RouterEntry, StandardSchemaError, StandardSchemaV1, TimeoutError, ValidationError, type ValidationErrorContext, type ValidationMode, type Validator, type ValidatorOutput, createApi, createExecutor, createFetchExecutor, definePlugin, defineRouter, dispatchExecutor, endpoint, isRouterDef, joinPaths, logger, resolvePath, runValidator, serializeParams };
|