@stackonward/api-client
v0.0.3
Published
Framework-agnostic HTTP transport core with injectable auth, error normalization, and retry control flow
Maintainers
Readme
@stackonward/api-client
Framework-independent HTTP transport built on Fetch with injectable headers, authentication, error normalization, timeouts, and bounded session refresh.
The package preserves transport semantics. It does not know a provider's response envelope, navigate the UI, or convert failed requests into empty data.
Install
pnpm add @stackonward/api-clientQuick start
import { createApiClient } from "@stackonward/api-client";
const api = createApiClient({
baseUrl: "https://api.example.com/v1",
timeout: 10_000,
headers: ({ method }) => ({ "X-Request-Method": method }),
});
const project = await api.get<{ id: string; name: string }>("/projects/current");
await api.post<void>("/events", { name: "project_viewed" });The generic type parameter describes the expected compile-time type; it does not validate an external response. Parse untrusted provider data in the owning integration before treating it as trusted application data.
Client configuration
| Option | Default | Purpose |
| ----------------- | ------------------ | ---------------------------------------------------------------- |
| baseUrl | required | Base URL joined with each request path |
| fetch | globalThis.fetch | Fetch-compatible transport implementation |
| timeout | 30000 | Client-wide request timeout in milliseconds |
| maxRetries | 1 | Maximum session-invalid refresh retries |
| headers | none | Static headers or a provider evaluated for every send |
| tokenStore | none | Session-scoped token access, refresh, and clearing port |
| errorNormalizer | passthrough | Provider-specific error projection and session-invalid predicate |
Per-request options support query, headers, body, requireAuth, an
explicit accessToken, signal, and a timeout override. Convenience methods
are available for get, post, put, patch, and delete.
Authentication and retry
import type { ErrorNormalizer, TokenStore } from "@stackonward/api-client";
import { createApiClient } from "@stackonward/api-client";
export function createAuthenticatedApi(tokenStore: TokenStore, errorNormalizer: ErrorNormalizer) {
return createApiClient({
baseUrl: "https://api.example.com/v1",
tokenStore,
errorNormalizer,
});
}requireAuthfails before network dispatch when no token is available.shouldRefreshcan trigger a proactive refresh before the first send.- Only errors classified by
isSessionInvalidcan trigger refresh and retry. - An explicit access token bypasses refresh and is never replaced with another identity.
- Network errors and timeouts are terminal for the request and are not replayed.
- Dynamic headers and the current stored token are resolved again for each retry.
The TokenStore implementation owns single-flight refresh. Keep one store per
browser session and create a fresh store for every SSR request; a process-global
server store can leak identity between users.
Responses and errors
- JSON and structured
+jsonsuccess responses are parsed as JSON. - Other success responses are returned as text.
- HTTP 204 and 205 return
undefined. - Plain object bodies are serialized as JSON; native body types pass through.
- Non-2xx responses enter the configured
ErrorNormalizerasApiHttpError. - Final errors are
NormalizedApiErrorinstances with transport kind, status, provider code, safe message, optional field errors, body, and cause.
The transport preserves diagnostic bodies but never logs them automatically. Consumers are responsible for avoiding sensitive data in application logs.
Public API
| Export | Purpose |
| ------------------------------------------------- | ----------------------------------------------------------- |
| createApiClient | Create an isolated client instance |
| ApiHttpError | Raw non-2xx status, response, and decoded body for adapters |
| NormalizedApiError | Stable throwable error projected for consumers |
| ApiClient, ApiClientConfig, RequestOptions | Client and request contracts |
| TokenStore, ErrorNormalizer, HeaderProvider | Replaceable authentication and provider adapter ports |
Compatibility
- ESM runtime with a Fetch, Headers, Response, and AbortController implementation
- Modern browsers, Node.js 18 or newer, Bun, or compatible workers
- TypeScript declarations included
Related packages
@stackonward/cms-clientbuilds a structured CMS client on this transport.@stackonward/onex-cms-clientsupplies OneX headers and error normalization.@stackonward/nuxt-layer-base/runtimeadapts session state toTokenStore.
License
MIT
