@vxil/sdk 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -6
- package/dist/index.d.ts +753 -58
- package/dist/index.js +402 -267
- package/dist/qs.d.ts +15 -0
- package/dist/qs.js +48 -0
- package/dist/retry.d.ts +47 -0
- package/dist/retry.js +156 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @vxil/sdk
|
|
2
2
|
|
|
3
|
-
Typed JavaScript/TypeScript client for the [Vxil](https://vxil.com) REST API — backend building blocks (auth, cms, files, payments, notifications, jobs, realtime, vector search, AI, and more) you enable in one line.
|
|
3
|
+
Typed JavaScript/TypeScript client for the [Vxil](https://vxil.com) REST API — backend building blocks (auth, cms, files, payments integration, notifications, jobs, realtime, vector search, AI, and more) you enable in one line.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @vxil/sdk
|
|
@@ -13,14 +13,91 @@ const vx = new Vxil({ apiKey: process.env.VXIL_API_KEY! });
|
|
|
13
13
|
|
|
14
14
|
await vx.users.upsert({ id: 'u_1', email: 'ada@example.com' });
|
|
15
15
|
await vx.notifications.send({ user_id: 'u_1', template: 'welcome', data: { app_name: 'MyApp' } });
|
|
16
|
-
const items = await vx.from('tasks').
|
|
16
|
+
const { items } = await vx.from('tasks').query({ filter: { status: 'open' } });
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
- Zero dependencies
|
|
19
|
+
- Zero dependencies. Runs anywhere `fetch` exists: Node ≥ 18, browsers, edge runtimes, and **React Native / Expo** (Hermes) — the SDK uses none of the WHATWG `URL` / `URLSearchParams` surface React Native only partially provides.
|
|
20
20
|
- Defaults to `https://api.vxil.com`; pass `baseUrl` to target another environment.
|
|
21
|
-
- Every feature the tenant has enabled is available as a typed namespace; generate a
|
|
22
|
-
|
|
21
|
+
- Every feature the tenant has enabled is available as a typed namespace; generate a project-exact client with `npx @vxil/cli gen`.
|
|
22
|
+
- Every non-2xx response throws a `VxilError` carrying the structured error envelope (`status`, `code`, `message`, `hint`, `fixUrl`, `requestId`, and `retryAfter` in seconds when the server sent `Retry-After`).
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Server mode and end-user mode
|
|
25
|
+
|
|
26
|
+
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:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const vx = new Vxil({
|
|
30
|
+
apiKey: PUBLIC_VXIL_KEY, // thin-client key: reads + owner-scoped writes only
|
|
31
|
+
endUserToken: session.token, // from vx.auth.signIn / your sign-in flow
|
|
32
|
+
});
|
|
33
|
+
// reads and writes on owner-scoped resources are confined to this user
|
|
34
|
+
const { items } = await vx.from('meals').query({ limit: 20 });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`vx.asEndUser(token)` returns a client for another session with everything else inherited; nothing in the SDK caches a token.
|
|
38
|
+
|
|
39
|
+
## Retries, timeouts and request hooks
|
|
40
|
+
|
|
41
|
+
All three are **off by default** — with none of them set the client is a single `fetch` per call, exactly as before.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const vx = new Vxil({
|
|
45
|
+
apiKey,
|
|
46
|
+
timeoutMs: 10_000, // per attempt; aborts the request AND its body read
|
|
47
|
+
retry: { attempts: 2 }, // up to 3 requests in total
|
|
48
|
+
hooks: {
|
|
49
|
+
beforeRequest: (req) => ({ 'x-request-id': crypto.randomUUID() }),
|
|
50
|
+
afterResponse: ({ request, response, durationMs }) => log(request.method, request.url, response.status, durationMs),
|
|
51
|
+
onRetry: ({ request, delayMs, status, error }) => log('retry', request.url, delayMs, status ?? error),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Option | Default | Meaning |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| `retry.attempts` | `0` (off) | Retries after the first attempt. |
|
|
59
|
+
| `retry.retryOn` | `[429, 502, 503, 504]` | Response statuses that trigger a retry. |
|
|
60
|
+
| `retry.backoffMs` | `250` | First delay; doubles per retry, with jitter in `[½, 1]` of the computed delay. |
|
|
61
|
+
| `retry.maxBackoffMs` | `10_000` | Longest wait between attempts. A `Retry-After` beyond it ends the loop instead of waiting. |
|
|
62
|
+
| `retry.respectRetryAfter` | `true` | Use the response's `Retry-After` (seconds or HTTP-date) as the delay when present. |
|
|
63
|
+
| `retry.retryOnNetworkError` | `true` | Also retry when `fetch` itself fails (DNS, reset, a `timeoutMs` timeout). |
|
|
64
|
+
| `timeoutMs` | none | Per-attempt timeout via `AbortController`; throws `VxilError` `{ status: 0, code: 'request_timeout' }`. |
|
|
65
|
+
| `hooks.beforeRequest` | — | Runs before every attempt; may return headers to add for that attempt. |
|
|
66
|
+
| `hooks.afterResponse` | — | Runs after every response (retried or final) with the response and its duration. |
|
|
67
|
+
| `hooks.onRetry` | — | Runs right before the client sleeps for a retry. |
|
|
68
|
+
|
|
69
|
+
Rules that keep retries safe:
|
|
70
|
+
|
|
71
|
+
- **Only idempotent requests are ever retried**: `GET`, `HEAD`, `PUT`, `DELETE`, and a `POST` only when the call carries an `Idempotency-Key` (the `{ idempotencyKey }` option on the money routes — `payments.credits.consume`, `notifications.send`, …). A bare `POST` or a `PATCH` is never retried, whatever `retry` says.
|
|
72
|
+
- 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 with `vx.asEndUser(token)`.
|
|
73
|
+
- A thrown hook aborts the call. The body of a response a hook sees has already been read; inspect `status` and `headers`.
|
|
74
|
+
|
|
75
|
+
## Errors
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { VxilError } from '@vxil/sdk';
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await vx.payments.credits.consume({ user_id, credit_type: 'tokens', amount: 5 }, { idempotencyKey });
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (e instanceof VxilError && e.status === 429) {
|
|
84
|
+
showToast(`Try again in ${Math.ceil(e.retryAfter ?? 30)}s`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Changelog
|
|
90
|
+
|
|
91
|
+
### 0.4.0 — 2026-09-11
|
|
92
|
+
|
|
93
|
+
- React-Native-clean: every query string is built without `URLSearchParams` (React Native's polyfill throws on `.set` before 0.81); wire bytes unchanged.
|
|
94
|
+
- `VxilError.retryAfter` (seconds) parsed from `Retry-After`.
|
|
95
|
+
- Opt-in `retry`, `timeoutMs` and `hooks` options on the client (see above). No behaviour changes unless set.
|
|
96
|
+
|
|
97
|
+
### 0.3.0
|
|
98
|
+
|
|
99
|
+
- Agent inner-loop wave: structured output, usage, `@vxil/react` companions.
|
|
100
|
+
|
|
101
|
+
Docs: [vxil.com/docs/guide](https://vxil.com/docs/guide) · Dashboard: [vxil.com/dashboard](https://vxil.com/dashboard) · Terms: [vxil.com/terms](https://vxil.com/terms)
|
|
25
102
|
|
|
26
103
|
MIT © techmaker.io
|