createBoardClient
Configure storage, headers, hooks, logging, and the exact Board API request pipeline.
A
JcreateBoardClient(options) creates the complete typed Board SDK. A client is safe to reuse across public requests. A shared server client is also safe when it uses nostore and receives user credentials per call.
Signature
12345678import {createBoardClient,type BoardSdk,type CreateBoardClientOptions,} from '@cavuno/board';const create: (options: CreateBoardClientOptions) => BoardSdk =createBoardClient;
Options
| Option | Required | Exact behavior |
|---|---|---|
baseUrl | No | API origin. Default: https://api.cavuno.com. Override only when Cavuno supplies a staging or development origin. Trailing slashes are removed before the client appends /v1/boards/{encodedBoard}. |
board | Yes | pk_… publishable key, boards_… ID, or slug. The value is URL-encoded in the base path and scopes persistent browser storage. |
auth.storage | No | memory, nostore, local, session, or a CustomStorage. Default: memory when globalThis.document exists; nostore otherwise. |
globalHeaders | No | Headers applied to every request after SDK defaults and before stored or per-call credentials. |
onRequest | No | Sync or async hook that receives the complete URL and RequestInit; it may mutate or replace the request. |
onResponse | No | Sync or async side-effect hook that receives a response clone and the final request before response parsing. |
logger | No | { debug(message), error(message) }. Debug receives request and response lines; error receives a typed API error line. |
Create a browser client
123456789// src/lib/cavuno.tsimport { createBoardClient } from '@cavuno/board';export const board = createBoardClient({board: 'pk_example',auth: { storage: 'session' },});const context = await board.context();
Storage modes have intentionally different lifetimes:
memoryis private to this client and disappears on reload.sessionusessessionStorageand survives reloads in one tab.localuseslocalStorageand survives browser restarts.nostorenever retains tokens.- A custom adapter owns persistence, board scoping, and user isolation.
local and session throw during client creation when used outside a browser or when Web Storage access is blocked. Their keys are scoped by the board identifier so two boards on one origin do not share tokens.
Create a stateless server client
1234567891011121314// src/lib/cavuno.server.tsimport { createBoardClient } from '@cavuno/board';export const board = createBoardClient({board: process.env.PUBLIC_CAVUNO_BOARD!,auth: { storage: 'nostore' },});export function getMe(accessToken: string) {return board.me.retrieve(undefined, {cache: 'no-store',headers: { authorization: `Bearer ${accessToken}` },});}
Store the token pair in an application-owned httpOnly cookie. Never attach one user’s storage adapter to a module-scoped server client.
Use custom storage
12345678910111213import type { CustomStorage } from '@cavuno/board';const values = new Map<string, string>();const storage: CustomStorage = {getItem: (key) => values.get(key) ?? null,setItem: (key, value) => {values.set(key, value);},removeItem: (key) => {values.delete(key);},};
The SDK reads the access token on every request. Auth methods write and clear the access/refresh pair. Logging out deliberately leaves the board-password grant intact because it is board-scoped, not user-scoped.
Request pipeline
For every board.client.fetch or namespace call, the client performs these steps in order:
- Remove trailing slashes from
baseUrland build/v1/boards/{encodedBoard}{path}. - Serialize a flat query. Arrays become repeated keys;
nullandundefinedvalues are omitted. - Separate
bodyandquery; pass every otherFetchOptionsfield through tofetch. - JSON-stringify ordinary bodies and add
content-type: application/json. Strings,URLSearchParams,FormData,Blob,ArrayBuffer, andReadableStreampass through unchanged. - Merge headers in this order:
accept: application/json, JSON content type,globalHeaders, stored bearer token, storedX-Board-Accessgrant, then per-call headers. Later values win. - Add
X-Cavuno-Sdk: board@{SDK_VERSION}only when the request is non-GET or already carries authorization/board access. An explicit header is not replaced. - Run and await
onRequest, then log and callglobalThis.fetch. - Run and await
onResponsewith a clone, so the hook cannot consume the SDK’s response body. - Return
undefinedfor HTTP 204; parse successful non-204 responses as JSON. - For non-2xx responses, parse the error envelope and throw
BoardApiError. An unparseable envelope becomesunknown_error.
The client never automatically retries a request and never refreshes a session on 401.
Hook the pipeline
1234567891011121314const board = createBoardClient({board: 'pk_example',globalHeaders: { 'x-app-version': '2026.07.10' },onRequest(request) {request.init.headers = new Headers(request.init.headers);request.init.headers.set('x-request-source', 'custom-board');return request;},onResponse(response, request) {if (!response.ok) {console.error(response.status, request.url);}},});
Do not log authorization headers, board-access grants, or private response bodies.
Verify the client
Call board.context() and inspect the network request. It should target /v1/boards/{identifier}, include accept: application/json, and omit X-Cavuno-Sdk on an anonymous GET. An authenticated read should include both the bearer and SDK version headers.
Unknown storage modes fail during construction. Board resolution and all other non-2xx request failures use BoardApiError.