Client contract
@nelcota/client 0.1.x targets the Nelcota HTTP protocol implemented by this
repository. The package has its own version; server compatibility is determined
by the endpoints and features below, not by matching package version numbers.
Version scope
This is the current source contract. Published 0.1.2 predates native IndexedDB journaling and exact numeric preservation below; consult its bundled package docs for that release. Unreleased client behavior requires a rebuilt/new client package, independently of a server update.
Interface
| Module | Methods | HTTP contract |
|---|---|---|
| REST | from().select(), filters, order, limit, range, single, maybeSingle |
GET/HEAD /rest/v1/{table}; encoded query; optional Prefer: count=exact; Content-Range |
| REST writes | insert, update, delete, upsert, optional select | POST/PATCH/DELETE /rest/v1/{table}; JSON body; Prefer: return=minimal/representation; upsert resolution and on_conflict |
| RPC | rpc(name, args) |
POST /rest/v1/rpc/{name}; named JSON arguments; caller's role |
| Auth | signUp | POST /auth/v1/signup; session or {user} awaiting confirmation |
| Auth | password login, refresh, exchangeCode | POST /auth/v1/token?grant_type=password/refresh_token/pkce |
| Auth | getUser, signOut | GET /auth/v1/user, POST /auth/v1/logout |
| Auth links | recover, magic link, resend, verify/reset | POST /auth/v1/recover, /magiclink, /resend, /verify |
| OAuth | signInWithOAuth, handleRedirect | GET /auth/v1/authorize; S256 PKCE; callback ?code= exchanged once |
| Storage | upload, replace, open/download, remove | POST/PUT/GET/DELETE /storage/v1/object/{bucket}/{path}; raw bytes |
| Storage | list, signed URL, public URL | POST /storage/v1/object/list/{bucket}, /object/sign/{bucket}/{path}; /object/public/{bucket}/{path} |
| Buckets | list, get, create, update, delete | /storage/v1/bucket and /storage/v1/bucket/{id} |
schema selects the generated TypeScript schema; the server's configured
exposed schema determines the HTTP resource. It does not switch database schemas
at runtime. RLS and GRANTs always remain server/Postgres decisions.
Results and failures
- Auth and storage return
{data,error}. REST addsstatusandcount; RPC addsstatus. Writes without.select()and HEAD responses return null. HEAD is typed as null, including chained selects.single/maybeSinglerequire a row representation and refuse minimal writes or HEAD before HTTP. A requested row representation that is empty or not a JSON array returnsinvalid_response; it never succeeds with null or another shape. - HTTP failures preserve server
code,messageand status. Network, abort, timeout and interrupted buffered bodies return status 0 and client codes. - HTTP error bodies larger than 64 KiB are discarded; status, a generic code and Retry-After remain available.
- Invalid JSON returns
invalid_responsewith the response's HTTP status. - JSON numbers stay numeric when their decimal digits survive JavaScript
conversion. Unsafe integers and decimals that would lose digits become
strings, including nested JSON values and RPC results. Generated
bigint,numeric,realanddouble precisionfields usenumber | string; regenerate schema types after upgrading. Pass exact values as strings in filters and writes. PostgreSQL array elements may be null independently of column nullability; generated arrays therefore use(T | null)[]. - Scalar RPC result sets also allow null elements and group numeric unions as
(number | string | null)[]. Functions returning view rows reference the generatedViewssection, while table rows referenceTables. - Non-finite JSON input (
NaNand infinities) throwsNelcotaUsageErrorbefore authentication or HTTP, instead of silently becoming null. Explicit nulls remain valid. Sessions require the complete session/user shape, withexpires_atandexpires_inas safe, non-negative integers;refreshMarginmust be finite and non-negative. - Invalid developer configuration and unsafe identifiers/paths throw
NelcotaUsageError. Errors thrown by user-supplied token/storage/fetch configuration outside the HTTP operation remain the caller's responsibility. - GET and HEAD retry network failures, body-read failures, 429 and 503; mutations, auth token exchange, storage listing and RPC are not automatically retried.
- Timeout covers each attempt and its buffered body, excluding session refresh
and the wait between retries. Retry-After up to 30 seconds is respected;
longer delays return the error immediately with
retryAfterfor the caller. storage.open()hands the raw response to the caller. Subsequent stream errors must be handled by the caller;download()buffers into a Blob and returns body failures in its result.
Sessions and lifecycle
Each client owns its session adapter. Native localStorage uses an IndexedDB journal as authoritative session state, with localStorage as a compatibility mirror. Elsewhere the default is a separate in-memory adapter. A custom adapter can be sync or async. Instantiate a server client per request or supply a per-request token. The default storage key includes the host and base path, so projects under different paths cannot overwrite each other's sessions in a shared adapter.
Default server-memory clients use independent coordination queues. A pending sign-in on another client cannot delay reading an unrelated request's session.
Refresh calls on one auth client share a promise. Clients sharing an adapter and storage key are serialized with Web Locks, with a module-local queue as a fallback. Native browser sessions also commit a refresh reservation before HTTP and read the replacement only after the IndexedDB transaction commits. Web Locks alone do not order another tab's localStorage visibility. Without Web Locks, the journal rejects competing owners instead of waiting for their replacement. Custom shared adapters require Web Locks or external coordination; otherwise use per-tab sessionStorage. The module-local queue cannot coordinate separate tabs.
Native persistence requires IndexedDB and bounds each database open and transaction to five seconds. Connections close after every operation. A foreign unfinished reservation requires a new sign-in, an owning-client retry, or signOut; it never falls back to the potentially spent token in localStorage. A committed logout supersedes dirty state in other clients and late refresh responses cannot overwrite it. Broadcasts carry change notifications, not tokens.
Each native publication has a unique revision. Explicit sign-in captures the prior receipt before HTTP; failed publication retains the local replacement or logout while that exact receipt remains current, even with a foreign owner. A later committed revision supersedes it even when the value is identical.
The journal imports existing localStorage state once per storage key. Reload all
tabs when upgrading to avoid mixing SDK coordination protocols. Use signOut()
or clear all site data to remove sessions; editing only localStorage no longer
changes the authoritative session. Custom adapters retain their existing semantics.
The refresh margin is capped at half the token lifetime, so short-lived tokens and large configured margins do not rotate fresh sessions repeatedly. Background jitter stays within the scheduled delay. An already expired token waits ten seconds before a background attempt; explicit session reads still refresh it immediately.
Auth methods that perform HTTP accept {signal,timeout} as the last argument.
Concurrent refresh callers share the first caller's timeout. Signals cancel
each caller's wait independently; rotation continues and persists its result
even when a waiter cancels. An already aborted caller does not start rotation.
REST/storage signals also cover waiting for the token source or session
refresh, without aborting an operation shared by other callers. signOut() waits
for a pending refresh and clears locally even when server revocation fails.
dispose() stops timers, closes channels and clears listeners; it neither
revokes the session nor aborts requests already in flight.
Native PKCE verifiers use the same journal with a separate key. Consumption commits a tombstone atomically, preventing stale native views from exchanging with an older or already consumed verifier. Cancellation before consumption keeps the verifier; once consumption starts, cancellation ends the caller's wait while the bounded exchange finishes and persists an accepted session. Custom adapters keep their existing storage API and keyed-lock coordination.
Failed session writes keep the newer state in memory: this client never reloads
a spent refresh token or restores a signed-out session from stale storage.
Storage errors still reject explicit auth methods. Unexpected background
adapter errors are observed through the optional auth.onBackgroundError
handler; automatic refresh pauses until a session is written successfully.
Handler exceptions are isolated. Network errors returned in results keep the
existing bounded refresh retry. Native clients block on another owner's
unfinished reservation; an accepted replacement remains with its owner until
it can be persisted or a newer committed session or logout supersedes it.
Custom adapters require repair before sharing state that failed to persist.
An explicit accessToken controls REST/storage requests and disables automatic
session refresh in the combined client. Auth methods remain available and may
store a session when called explicitly.
Bucket settings
Creation accepts BucketSettings with optional fields. Updating uses
BucketUpdateSettings, requires public, file_size_limit and
allowed_mime_types, and replaces the entire settings object with PUT.
Use explicit null to clear a limit. Missing fields throw NelcotaUsageError
before any HTTP request, including in JavaScript consumers.
Release acceptance
Run check, unit/type tests, build, size, test:package, runtime smoke tests
and test:contract -- --browser. CI tests Node 22/24/26, Bun, Deno and
Chromium/Firefox/WebKit. Edge hosts may consume the Web API implementation but
are not included in this compatibility guarantee until tested on that host.