@vxil/sdk
v0.7.0
Published
Typed client for the Vxil REST API (notifications, auth, jobs, files, cms, comments, webhooks, realtime, orgs, rate-limits).
Maintainers
Readme
@vxil/sdk
Typed JavaScript/TypeScript client for the Vxil REST API — backend building blocks (auth, cms, files, payments integration, notifications, jobs, realtime, vector search, AI, and more) you enable in one line.
npm install @vxil/sdkimport { Vxil } from '@vxil/sdk';
const vx = new Vxil({ apiKey: process.env.VXIL_API_KEY! });
await vx.users.upsert({ id: 'u_1', email: '[email protected]' });
await vx.notifications.send({ user_id: 'u_1', template: 'welcome', data: { app_name: 'MyApp' } });
const { items } = await vx.from('tasks').query({ filter: { status: 'open' } });- Zero dependencies. Runs anywhere
fetchexists: Node ≥ 18, browsers, edge runtimes, and React Native / Expo (Hermes) — the SDK uses none of the WHATWGURL/URLSearchParamssurface React Native only partially provides. - Defaults to
https://api.vxil.com; passbaseUrlto target another environment. - Every feature the tenant has enabled is available as a typed namespace; generate a project-exact client with
npx @vxil/cli gen. - Every non-2xx response throws a
VxilErrorcarrying the structured error envelope (status,code,message,hint,fixUrl,requestId, andretryAfterin seconds when the server sentRetry-After).
Server mode and end-user mode
A server key is a secret: keep it in a server route handler and never ship it in a browser or app bundle. To call Vxil from a browser or a phone directly, use a thin-client key (the end_user_required class — it refuses any request without a valid end-user session) together with the signed-in user's session token:
const vx = new Vxil({
apiKey: PUBLIC_VXIL_KEY, // thin-client key: reads + owner-scoped writes only
endUserToken: session.token, // from vx.auth.signIn / your sign-in flow
});
// reads and writes on owner-scoped resources are confined to this user
const { items } = await vx.from('meals').query({ limit: 20 });vx.asEndUser(token) returns a client for another session with everything else inherited; nothing in the SDK caches a token.
Retries, timeouts and request hooks
All three are off by default — with none of them set the client is a single fetch per call, exactly as before.
const vx = new Vxil({
apiKey,
timeoutMs: 10_000, // per attempt; aborts the request AND its body read
retry: { attempts: 2 }, // up to 3 requests in total
hooks: {
beforeRequest: (req) => ({ 'x-request-id': crypto.randomUUID() }),
afterResponse: ({ request, response, durationMs }) => log(request.method, request.url, response.status, durationMs),
onRetry: ({ request, delayMs, status, error }) => log('retry', request.url, delayMs, status ?? error),
},
});| Option | Default | Meaning |
| --- | --- | --- |
| retry.attempts | 0 (off) | Retries after the first attempt. |
| retry.retryOn | [429, 502, 503, 504] | Response statuses that trigger a retry. |
| retry.backoffMs | 250 | First delay; doubles per retry, with jitter in [½, 1] of the computed delay. |
| retry.maxBackoffMs | 10_000 | Longest wait between attempts. A Retry-After beyond it ends the loop instead of waiting. |
| retry.respectRetryAfter | true | Use the response's Retry-After (seconds or HTTP-date) as the delay when present. |
| retry.retryOnNetworkError | true | Also retry when fetch itself fails (DNS, reset, a timeoutMs timeout). |
| timeoutMs | none | Per-attempt timeout via AbortController; throws VxilError { status: 0, code: 'request_timeout' }. |
| hooks.beforeRequest | — | Runs before every attempt; may return headers to add for that attempt. |
| hooks.afterResponse | — | Runs after every response (retried or final) with the response and its duration. |
| hooks.onRetry | — | Runs right before the client sleeps for a retry. |
Rules that keep retries safe:
- Only idempotent requests are ever retried:
GET,HEAD,PUT,DELETE, and aPOSTonly when the call carries anIdempotency-Key(the{ idempotencyKey }option on the money routes —payments.credits.consume,notifications.send, …). A barePOSTor aPATCHis never retried, whateverretrysays. - Hooks receive a frozen request descriptor (
method,url,headers,attempt) that never includes the credential headers, and they cannot set them either; rotate a session withvx.asEndUser(token). - A thrown hook aborts the call. The body of a response a hook sees has already been read; inspect
statusandheaders.
Errors
import { VxilError } from '@vxil/sdk';
try {
await vx.payments.credits.consume({ user_id, credit_type: 'tokens', amount: 5 }, { idempotencyKey });
} catch (e) {
if (e instanceof VxilError && e.status === 429) {
showToast(`Try again in ${Math.ceil(e.retryAfter ?? 30)}s`);
}
}Changelog
0.4.1 — 2026-09-13
- Documentation only: JSDoc no longer cites internal repository paths; points at the public guide instead. No runtime change.
0.4.0 — 2026-09-11
- React-Native-clean: every query string is built without
URLSearchParams(React Native's polyfill throws on.setbefore 0.81); wire bytes unchanged. VxilError.retryAfter(seconds) parsed fromRetry-After.- Opt-in
retry,timeoutMsandhooksoptions on the client (see above). No behaviour changes unless set.
0.3.0
- Agent inner-loop wave: structured output, usage,
@vxil/reactcompanions.
Docs: vxil.com/docs/guide · Dashboard: vxil.com/dashboard · Terms: vxil.com/terms
MIT © techmaker.io
