### Installation Source: https://routar.vercel.app/llms-full.txt Installation instructions for different package managers and HTTP clients. ```bash # with fetch (built into core — zero extra dependencies) npm install @routar/core # with axios npm install @routar/core @routar/axios axios # with ky npm install @routar/core @routar/ky ky # for testing with MSW npm install @routar/core msw npm install --save-dev @routar/msw ``` -------------------------------- ### With adapter example Source: https://routar.vercel.app/llms-full.txt Example showing how to use an adapter to transform the raw response data. ```typescript const TodoRawSchema = z.object({ id: z.number(), title: z.string(), is_done: z.boolean() }); endpoint({ method: 'GET', path: '/:id', request: z.object({ path: z.object({ id: z.number() }) }), response: TodoRawSchema, adapter: (raw) => ({ id: raw.id, title: raw.title, completed: raw.is_done, }), }); ``` -------------------------------- ### Basic setup Source: https://routar.vercel.app/llms-full.txt Generates MSW v2 HttpHandler[] from a RouterDef for use in tests. ```typescript import { createMswHandlers } from '@routar/msw'; import { HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { todoRouter } from './todo.api'; // Basic setup const server = setupServer( ...createMswHandlers(todoRouter, 'https://api.example.com', { getList: () => HttpResponse.json([{ id: 1, title: 'Buy milk', completed: false }]), getDetail: ({ params }) => HttpResponse.json({ id: params.id, title: 'Buy milk', completed: false }), create: ({ body }) => HttpResponse.json({ id: 2, title: body.title, completed: false }), }), ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); ``` -------------------------------- ### Core Pattern Source: https://routar.vercel.app/llms-full.txt Example demonstrating the core pattern of defining a router, creating an API executor, and making fully-typed API calls. ```typescript import { z } from 'zod'; import { endpoint, defineRouter, createApi, createFetchExecutor } from '@routar/core'; const TodoSchema = z.object({ id: z.number(), title: z.string(), completed: z.boolean() }); const todoRouter = defineRouter('/todos', { getList: endpoint({ method: 'GET', path: '/', response: z.array(TodoSchema) }), getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema, request: z.object({ path: z.object({ id: z.number() }) }) }), create: endpoint({ method: 'POST', path: '/', response: TodoSchema, request: z.object({ body: z.object({ title: z.string() }) }) }), update: endpoint({ method: 'PATCH', path: '/:id', response: TodoSchema, request: z.object({ path: z.object({ id: z.number() }), body: z.object({ completed: z.boolean() }), }) }), remove: endpoint({ method: 'DELETE', path: '/:id', request: z.object({ path: z.object({ id: z.number() }) }), response: z.object({}) }), }); const executor = createFetchExecutor('https://api.example.com'); const todoApi = createApi(executor, todoRouter); // Fully-typed calls — no type annotations needed const todos = await todoApi.getList({}); // Todo[] const todo = await todoApi.getDetail({ path: { id: 1 } }); // Todo const next = await todoApi.create({ body: { title: 'buy milk' } }); // Todo await todoApi.update({ path: { id: 1 }, body: { completed: true } }); await todoApi.remove({ path: { id: 1 } }); ``` -------------------------------- ### Request shape examples Source: https://routar.vercel.app/llms-full.txt Examples illustrating different request shapes including query parameters, path parameters, and body. ```typescript // query params endpoint({ method: 'GET', path: '/search', request: z.object({ query: z.object({ q: z.string(), limit: z.number().optional() }) }), response: z.array(TodoSchema), }); // path + body endpoint({ method: 'PUT', path: '/:id', request: z.object({ path: z.object({ id: z.number() }), body: z.object({ title: z.string(), completed: z.boolean() }), }), response: TodoSchema, }); ``` -------------------------------- ### withTimeout Middleware Source: https://routar.vercel.app/llms-full.txt Example of using the withTimeout middleware. ```typescript withTimeout(5_000) // abort after 5 seconds, throws TimeoutError ``` -------------------------------- ### Path param enforcement example Source: https://routar.vercel.app/llms-full.txt Example demonstrating compile-time error when path parameters are not matched in the request object. ```typescript // ✅ correct endpoint({ method: 'GET', path: '/:id', request: z.object({ path: z.object({ id: z.number() }) }), response: TodoSchema, }); // ❌ compile error — ':id' declared but request.path.id missing endpoint({ method: 'GET', path: '/:id', request: z.object({ query: z.object({ q: z.string() }) }), response: TodoSchema, }); ``` -------------------------------- ### withLogger Middleware Source: https://routar.vercel.app/llms-full.txt Examples of using the withLogger middleware with default and custom logging functions. ```typescript withLogger() // defaults to console.log withLogger({ log: (msg, data) => myLogger.debug(msg, data) }) ``` -------------------------------- ### Executor with Middlewares Source: https://routar.vercel.app/llms-full.txt Example of creating an executor with multiple middlewares like timeout, retry, and logger. ```typescript import { createExecutor, withTimeout, withRetry, withLogger, defineMiddleware } from '@routar/core'; import { HttpError } from '@routar/core'; const executor = createExecutor(transport, [ withTimeout(8_000), // abort after 8s withRetry(3, { shouldRetry: (err) => !(err instanceof HttpError && err.status < 500), }), withLogger({ log: (msg, data) => logger.debug(msg, data) }), ]); ``` -------------------------------- ### SSR/CSR Pattern (Next.js) Source: https://routar.vercel.app/llms-full.txt Example of setting up API executors for both server-side rendering (SSR) and client-side rendering (CSR) in a Next.js application. ```typescript // remote/lib/executor.ts import { dispatchExecutor, createFetchExecutor } from '@routar/core'; import { createAxiosExecutor } from '@routar/axios'; import axios from 'axios'; const serverExecutor = createFetchExecutor(process.env.API_URL!, { defaultHeaders: async () => { const { cookies } = await import('next/headers'); const token = (await cookies()).get('access_token')?.value; return token ? { Authorization: `Bearer ${token}` } : {}; }, }); const clientExecutor = createAxiosExecutor( axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL }), ); export const apiExecutor = dispatchExecutor(() => typeof window === 'undefined' ? serverExecutor : clientExecutor, ); // remote/services/todo/todo.api.ts import { createApi } from '@routar/core'; import { apiExecutor } from '../lib/executor'; export const todoApi = createApi(apiExecutor, todoRouter); // one client, works everywhere ``` -------------------------------- ### Error Handling Example Source: https://routar.vercel.app/llms-full.txt Demonstrates how to catch and handle different types of errors (ValidationError, HttpError, TimeoutError) thrown by Routar core functionalities. ```typescript import { TimeoutError, ValidationError, HttpError } from '@routar/core'; try { await todoApi.create({ body: { title: '' } }); } catch (err) { if (err instanceof ValidationError) console.log(err.message); if (err instanceof HttpError) console.log(err.status, err.body); if (err instanceof TimeoutError) console.log(`timed out after ${err.ms}ms`); } ``` -------------------------------- ### createApi Source: https://routar.vercel.app/llms-full.txt Builds a fully-typed API client. Each endpoint becomes an async function: `(params, signal?) => Promise`. ```typescript // preferred — pass the result of defineRouter const api = createApi(executor, todoRouter); // inline router with prefix const api = createApi(executor, '/todos', { getList: endpoint({ ... }) }); // no prefix — flat endpoint map const api = createApi(executor, { getList: endpoint({ ... }) }); ``` ```typescript // basic calls const todos = await todoApi.getList({}); const todo = await todoApi.getDetail({ path: { id: 1 } }); const next = await todoApi.create({ body: { title: 'buy milk' } }); // nested router access via dot notation const api = createApi(executor, apiRouter); await api.todos.getList({}); await api.users.getDetail({ path: { id: 1 } }); await api.users.todos.getList({}); // deeply nested // cancel in-flight requests with AbortSignal const controller = new AbortController(); const todos = await todoApi.getList({}, controller.signal); controller.abort(); // skip response validation in production const prodApi = createApi(executor, todoRouter, { validate: { request: true, response: process.env.NODE_ENV !== 'production' }, }); // extract types from the client — no duplication import type { ApiTypes } from '@routar/core'; type TodoApiTypes = ApiTypes; type Todo = TodoApiTypes['getDetail']['response']; // { id: number; title: string; completed: boolean } type CreateRequest = TodoApiTypes['create']['request']; // { body: { title: string } } ``` -------------------------------- ### Anti-Patterns: Separate API instances Source: https://routar.vercel.app/llms-full.txt Shows the anti-pattern of creating separate API instances for server and client, and the recommended approach using dispatchExecutor. ```typescript // ❌ Don't create separate server/client API instances const todoServerApi = createApi(serverExecutor, todoRouter); const todoClientApi = createApi(clientExecutor, todoRouter); // ✅ Use dispatchExecutor for one unified client export const todoApi = createApi(dispatchExecutor(() => typeof window === 'undefined' ? serverExecutor : clientExecutor, ), todoRouter); ``` -------------------------------- ### Anti-Patterns: Response transformation Source: https://routar.vercel.app/llms-full.txt Demonstrates the anti-pattern of using .transform() on responses and the recommended adapter pattern. ```typescript // ❌ Don't put .transform() on response — it breaks composition response: z.array(TodoRawSchema).transform(items => items.map(toTodoItem)) // ✅ Use adapter instead endpoint({ response: z.array(TodoRawSchema), adapter: (raw) => raw.map(toTodoItem), }) ``` -------------------------------- ### withRetry Middleware Source: https://routar.vercel.app/llms-full.txt Configuration options for the withRetry middleware. ```typescript // Retry all errors up to 3 times withRetry(3) // Retry only server errors (5xx), not client errors (4xx) withRetry(3, { shouldRetry: (err, attempt) => err instanceof HttpError && err.status >= 500, }) ``` -------------------------------- ### createFetchExecutor Source: https://routar.vercel.app/llms-full.txt Creates an executor using the native fetch API, suitable for SSR with dynamic headers. ```typescript import { createFetchExecutor } from '@routar/core'; // Minimal const executor = createFetchExecutor('https://api.example.com'); // With SSR dynamic headers const executor = createFetchExecutor('https://api.example.com', { defaultHeaders: async () => { const token = await getServerToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }, }); // Next.js App Router — forward cookies from the incoming request const executor = createFetchExecutor('https://api.example.com', { defaultHeaders: async () => { const { cookies } = await import('next/headers'); const token = (await cookies()).get('access_token')?.value; return token ? { Authorization: `Bearer ${token}` } : {}; }, middlewares: [withTimeout(8_000), withRetry(2)], }); ``` -------------------------------- ### Anti-Patterns: Path params in MSW Source: https://routar.vercel.app/llms-full.txt Illustrates the incorrect use of z.number() for path parameters in MSW and the correct use of z.coerce.number(). ```typescript // ❌ Don't use z.number() for path params in MSW (params are always strings) request: z.object({ path: z.object({ id: z.number() }) }) // ← in MSW context // ✅ Use z.coerce.number() in MSW resolvers request: z.object({ path: z.object({ id: z.coerce.number() }) }) ``` -------------------------------- ### createExecutor Source: https://routar.vercel.app/llms-full.txt Low-level factory used internally by all executor packages. Use to integrate any HTTP client. ```typescript import { createExecutor, withTimeout, withRetry, withLogger } from '@routar/core'; const executor = createExecutor( async ({ method, url, body, headers, signal }) => { const res = await myHttpClient.request({ method, url, body, headers, signal }); return res.data; }, [withTimeout(8_000), withRetry(3), withLogger()], ); ``` -------------------------------- ### createKyExecutor Source: https://routar.vercel.app/llms-full.txt Creates an executor using Ky, accepting a KyInstance or an async factory. ```typescript import ky from 'ky'; import { createKyExecutor } from '@routar/ky'; // CSR — shared instance with hooks const instance = ky.create({ prefixUrl: 'https://api.example.com', hooks: { beforeRequest: [(req) => { req.headers.set('Authorization', `Bearer ${getToken()}`); }], }, }); const executor = createKyExecutor(instance); ``` -------------------------------- ### defineMiddleware Source: https://routar.vercel.app/llms-full.txt Defines custom middleware functions for extending executor functionality. ```typescript // Add a correlation ID header to every request const withCorrelationId = defineMiddleware((opts, next) => next({ ...opts, headers: { ...opts.headers, 'X-Request-Id': crypto.randomUUID() } }) ); // Add auth header from a token store const withAuth = defineMiddleware(async (opts, next) => { const token = await tokenStore.get(); return next({ ...opts, headers: { ...opts.headers, Authorization: `Bearer ${token}` } }); }); ``` -------------------------------- ### HttpError Handling Source: https://routar.vercel.app/llms-full.txt Demonstrates how to catch and handle HttpError for non-2xx responses. ```typescript import { HttpError } from '@routar/core'; try { await todoApi.getDetail({ path: { id: 999 } }); } catch (err) { if (err instanceof HttpError) { console.log(err.status, err.statusText, err.body); // 404, 'Not Found', { message: '...' } } } ``` -------------------------------- ### Partial mocking Source: https://routar.vercel.app/llms-full.txt Only listed endpoints are intercepted. ```typescript createMswHandlers(todoRouter, 'https://api.example.com', { getList: () => HttpResponse.json([]), // getDetail, create, update, remove → not registered, requests reach the real server }); ``` -------------------------------- ### ApiTypes utility Source: https://routar.vercel.app/llms-full.txt Extracts { request, response } types for every endpoint from an API client. ```typescript import type { ApiTypes } from '@routar/core'; type TodoApiTypes = ApiTypes; type Todo = TodoApiTypes['getDetail']['response']; // { id: number; title: string; completed: boolean } type CreateRequest = TodoApiTypes['create']['request']; // { body: { title: string } } type GetListResponse = TodoApiTypes['getList']['response']; // Todo[] ``` -------------------------------- ### createAxiosExecutor Source: https://routar.vercel.app/llms-full.txt Creates an executor using Axios, supporting both CSR shared instances and SSR async factories. ```typescript import axios from 'axios'; import { createAxiosExecutor } from '@routar/axios'; // CSR — shared instance with interceptors const instance = axios.create({ baseURL: 'https://api.example.com' }); instance.interceptors.request.use((config) => { config.headers.Authorization = `Bearer ${getClientToken()}`; return config; }); const executor = createAxiosExecutor(instance); // SSR — factory, fresh instance per request const executor = createAxiosExecutor(async () => { const token = await getServerToken(); return axios.create({ baseURL: 'https://api.example.com', headers: { Authorization: `Bearer ${token}` }, }); }); ``` -------------------------------- ### dispatchExecutor Source: https://routar.vercel.app/llms-full.txt Creates an executor that selects the underlying transport at request time. Use to unify SSR and CSR behind a single API client. ```typescript import { dispatchExecutor, createFetchExecutor, createAxiosExecutor } from '@routar/core'; import axios from 'axios'; const serverExecutor = createFetchExecutor('https://api.example.com', { defaultHeaders: async () => { const { cookies } = await import('next/headers'); const token = (await cookies()).get('access_token')?.value; return token ? { Authorization: `Bearer ${token}` } : {}; }, }); const clientExecutor = createAxiosExecutor( axios.create({ baseURL: 'https://api.example.com' }), ); // Pick transport based on environment (SSR vs CSR) export const apiExecutor = dispatchExecutor(() => typeof window === 'undefined' ? serverExecutor : clientExecutor, ); // Or route by URL prefix const executor = dispatchExecutor((opts) => opts.url.startsWith('/internal') ? internalExecutor : publicExecutor, ); // One API client works in both SSR and CSR export const todoApi = createApi(apiExecutor, todoRouter); ``` -------------------------------- ### defineRouter Source: https://routar.vercel.app/llms-full.txt Groups endpoints under a shared URL prefix. Supports arbitrary nesting. ```typescript import { defineRouter } from '@routar/core'; // Flat router const todoRouter = defineRouter('/todos', { getList: endpoint({ method: 'GET', path: '/', response: z.array(TodoSchema) }), getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema, request: z.object({ path: z.object({ id: z.number() }) }) }), }); // Nested routers — mirror your URL structure const apiRouter = defineRouter('/api', { todos: todoRouter, // → /api/todos, /api/todos/:id users: defineRouter('/users', { getList: endpoint({ method: 'GET', path: '/', response: z.array(UserSchema) }), getDetail: endpoint({ method: 'GET', path: '/:id', response: UserSchema, request: z.object({ path: z.object({ id: z.number() }) }) }), }), }); ``` -------------------------------- ### Nested routers Source: https://routar.vercel.app/llms-full.txt Supports nested routers where the resolver map mirrors the router shape. ```typescript const apiRouter = defineRouter('/api', { todos: todoRouter, users: userRouter }); createMswHandlers(apiRouter, 'https://api.example.com', { todos: { getList: () => HttpResponse.json([]), getDetail: ({ params }) => HttpResponse.json({ id: Number(params.id) }), }, users: { getList: () => HttpResponse.json([]), }, }); ``` -------------------------------- ### PathParams utility Source: https://routar.vercel.app/llms-full.txt Extracts :param names from a path string as a union of string literals. ```typescript import type { PathParams } from '@routar/core'; type P = PathParams<'/:userId/posts/:postId'>; // 'userId' | 'postId' type Q = PathParams<'/todos'>; // never (no dynamic segments) ``` -------------------------------- ### endpoint(spec) Signature Source: https://routar.vercel.app/llms-full.txt The signature for the endpoint function, detailing its parameters and return type. ```typescript endpoint(spec: { method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; path: string; request?: Validator; // optional — for endpoints with no params response: Validator; adapter?: (raw: ValidatorOutput) => unknown; }): EndpointSpec ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.