Skip to content

@calternal/api-client

Typed shared HTTP transport; #484 adds bounded generated file/event codecs and explicit empty-response statuses. Empty 202/204 replies are shared transport semantics; all other JSON parsing stays strict. Failure envelopes and Retry-After survive the SPA/WebMCP boundary (#835). Browser 401 replies notify session owners without buffering streamed bodies; session epochs prevent stale replies (#555). Credentials and route authorization remain the server’s responsibility (DESIGN §§41, 48). Binary tool responses reserve room for base64 expansion within the shared 1 MiB result limit (#760).

Keep status, server details and Retry-After through SPA and WebMCP failures (#835, DESIGN §41).

  • Error

new ApiError(status, code, message, details?, payload?, retryAfter?): ApiError

Parameter Type
status number
code "bad_request" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "name_conflict_case" | "name_too_long" | "push_endpoint_rejected" | "too_many_requests" | "internal" | "service_unavailable"
message string
details? Record<string, string> | null
payload? unknown
retryAfter? string | null

ApiError

Error.constructor

readonly code: "bad_request" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "name_conflict_case" | "name_too_long" | "push_endpoint_rejected" | "too_many_requests" | "internal" | "service_unavailable"

readonly details: Record<string, string> | null | undefined

readonly payload: unknown

The complete decoded failure body, including future envelope fields (#835).

readonly retryAfter: string | null

A server delay hint, without enabling an automatic write replay (#835).

readonly status: number

ApiErrorCode = components["schemas"]["ErrorCode"]


ApiErrorEnvelope = components["schemas"]["ErrorEnvelope"]


ApiRequestOptions<Operation> = object & ParamOptions<Operation> & BodyOptions<Operation>

Name Type Description
baseUrl? string Base URL for a server or test environment.
cache? RequestCache Native fetch cache policy; no-store bypasses GET coalescing too.
emptyResponseStatuses? readonly number[] Additional successful statuses whose contract permits an empty body (#484).
headers? HeadersInit Extra request headers.
maxBytes? number -
pollMs? number -
requestContentType? string | null -
requestEncoding? "json" | "text" | "base64" | "none" Generated tool transport (#484); ordinary API calls keep strict JSON.
responseEncoding? "json" | "text" | "base64" | "sse" -
signal? AbortSignal Cancel this request.
Type Parameter
Operation

ApiResponse<Operation> = Operation extends object ? ResponseBody<Responses> : never

Type Parameter
Operation

const TIMEZONE_HEADER: "X-Calternal-Timezone" = "X-Calternal-Timezone"

The header that carries the active User IANA zone. Before a confirmed travel switch, this is the device zone. The server takes every day key (“today”, a Daily note, date:today) from it, never from UTC (#141).

apiErrorFromResponse(status, payload, retryAfter?): ApiError

Preserve server error fields and delay hints without replaying a write (#835, DESIGN §41).

Parameter Type
status number
payload unknown
retryAfter? string | null

ApiError


apiFetch<P, M>(path, method, …__namedParameters): Promise<ApiResponse<OperationAt<P, M>>>

Make one credentialed JSON API request. It carries the active User zone and treats empty 202/204 replies as success for shared write endpoints.

Type Parameter
P extends ApiPath
M extends "get" | "post" | "put" | "patch" | "delete" | "head" | "options"
Parameter Type
path P
method M
…__namedParameters ApiFetchOptionsArgument<OperationAt<P, M>>

Promise<ApiResponse<OperationAt<P, M>>>


deviceTimeZone(): string

The device’s IANA zone, the one source of the user’s day on this client. Some engines report a legacy alias (Asia/Calcutta); the server accepts every IANA name, including aliases.

string


fetchDeduped(input, init?): Promise<Response>

Share duplicate GETs at the transport layer. Each caller gets an independent Response and owns its own abort signal; the network request is cancelled only when every caller has left. Keep successful snapshots for a short coalescing window because a page and its shell can mount their readers in adjacent turns after the first response completes. Writes clear snapshots. Keeping this below JSON decoding lets generated and handwritten clients share the same request.

Parameter Type
input RequestInfo | URL
init? RequestInit

Promise<Response>


fetchSession(input, init): Promise<Response>

Notify browser owners of an expired session (#555). The web app owns cache cleanup/navigation; this transport has no dependency on the web storage module. Epochs ignore an old request’s 401 after sign-in, and bearer replies never end a browser session. Failed public authentication attempts do not end one either.

Parameter Type
input RequestInfo | URL
init RequestInit

Promise<Response>


setDeviceTimeZoneOverride(timeZone): void

Use the User-confirmed zone for API day keys after a travel switch.

Parameter Type
timeZone string | null

void