Version 0.1.0. Optional peers: ofetch ^1.5.1 (HTTP), @tanstack/query-core >=5.80.2 (query entries).
Nothing is sent with cookies unless you pass credentials: 'include'.
@vinicunca/request/orpc
export function createRpcClient<TClient extends NestedClient<RpcClientContext>>(
options: RpcClientOptions,
): TClient;
export interface RpcClientOptions {
url: string;
fetch?: RpcFetch;
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
credentials?: RequestCredentials;
}
export type ContractClient<TContract extends AnyContractRouter>
= ContractRouterClient<TContract, RpcClientContext>;
export const LIVE_RETRY: RpcClientContext;
// retry: Infinity
// shouldRetry: not (ORPCError && status < 500)
export { isDefinedError, ORPCError, safe } from '@orpc/client';
For router-inferred APIs pass RouterClient<typeof router, RpcClientContext> instead of ContractClient.
With skipLibCheck: false, install @opentelemetry/api (types only).
@vinicunca/request/orpc-query
export function createRpcQueryUtils<TClient extends NestedClient<any>>(
client: TClient,
options?: { path?: Array<string> },
): RouterUtils<TClient>;
Wraps createTanstackQueryUtils. path prefixes every query key.
@vinicunca/request/http
export function createHttpClient(clientOptions?: HttpClientOptions): HttpClient;
export const DEFAULT_TIMEOUT = 10_000;
export function isHttpResponse(value: unknown): value is HttpResponse;
export class HttpError extends Error {
kind: 'network' | 'timeout' | 'abort' | 'http' | 'envelope';
request: { url: string; method: string };
status?: number;
code?: unknown;
data?: unknown;
}
export function isHttpError(error: unknown): error is HttpError;
HttpClientOptions: baseURL, headers, credentials, timeout (default 10_000, 0 disables), retry, retryDelay, retryStatusCodes, responseReturn ('raw' | 'body' | 'data', default 'body'), arrayFormat, querySerializer, parseResponse, fetch.
Methods: get, post, put, patch, delete, head, request, plus addRequestInterceptor / addResponseInterceptor (each returns a remover) and raw (the ofetch instance — only inherits baseURL and injected fetch, not interceptors).
Request interceptors may mutate context.options. Response interceptors are a promise chain; rejected may recover. A fulfilled handler must return the received HttpResponse (or a spread of it). A freshly built object loses the internal brand — later handlers skip and responseReturn stops unwrapping.
responseReturn: 'data' returns the envelope payload when envelopeInterceptor is installed; a bad code throws HttpError{ kind: 'envelope' }.
Presets
Install envelope → refresh token → error message.
envelopeInterceptor({ codeField, dataField, successCode })
refreshTokenInterceptor({
client,
refresh, // MUST pass skipAuthRefresh: true on the refresh call
applyToken,
onAuthFailure,
enabled?,
})
errorMessageInterceptor({ notify, messages? })
DEFAULT_ERROR_MESSAGES
Concurrent 401s share one refresh(). Each request is retried once. skipAuthRefresh requests are never retried; their 401 still calls onAuthFailure (deferred while a refresh is in flight).
Query strings
Default: ofetch/ufo. Set arrayFormat for qs-style nested objects:
arrayFormat | { ids: [1, 2], user: { name: 'Ana' } } |
|---|---|
repeat | ids=1&ids=2&user[name]=Ana |
brackets | ids[]=1&ids[]=2&user[name]=Ana |
indices | ids[0]=1&ids[1]=2&user[name]=Ana |
comma | ids=1,2&user[name]=Ana |
null → key=; undefined omitted; Dates ISO; arrays of objects always use indices.
Interceptor-thrown exceptions propagate unwrapped (not as HttpError). Transport failures are always HttpError.
skipLibCheck: false also needs undici and @types/node (ofetch's types).
@vinicunca/request/http-query
export function createHttpQueryUtils(
client: HttpClient,
options?: { key?: ReadonlyArray<unknown> },
): HttpQueryUtils;
api.get<T>(path).queryOptions({ query, ...tanstack })
api.post<T, Vars>(path | (vars) => path).mutationOptions({ body?: (vars) => unknown, ... })
api.put / patch / delete // same as post
api.key() // [...root]
api.get(path).key() // [...root, 'GET', path]
Query keys: [...root, 'GET', path, { query }]. Request option keys (query, headers, credentials, timeout, responseReturn, responseType, arrayFormat, querySerializer, parseResponse) are stripped from TanStack options. retry / retryDelay / meta belong to TanStack here.
Cancelled queries abort the fetch. TanStack retries 3× by default, and each attempt runs the full interceptor chain (toasts, refresh). Prefer a retry that only retries 5xx.
queryOptions({...}) does not reject unknown keys — a typo like quer for query is silent, same as oRPC utils.