nodestack-client 1.3.0 → 2.1.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 CHANGED
@@ -1,341 +1,451 @@
1
- # 🚀 nodestack-client
2
-
3
- > **The Official Lightweight, Type-Safe JavaScript & TypeScript Client SDK for [NodeStack](https://github.com/satya-jugran/nodestack)**
4
- >
5
- > Published on npm: [`nodestack-client`](https://www.npmjs.com/package/nodestack-client)
6
-
7
- [![npm version](https://img.shields.io/npm/v/nodestack-client.svg?color=blue)](https://www.npmjs.com/package/nodestack-client)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
-
11
- A zero-dependency, isomorphic JavaScript and TypeScript client for NodeStack with out-of-the-box support for:
12
- - ⚡ **Zero runtime dependencies**: Built purely on standard Web APIs (`fetch`, `FormData`, `AbortController`, `EventSource`).
13
- - ðŸŠķ **Ultra-lightweight footprint**: `< 10 KB` minified + gzipped.
14
- - 🔒 **Pluggable & Reactive Auth**: `LocalAuthStore` (localStorage), `MemoryAuthStore` (Node/SSR/testing), and reactive `onChange` state subscriptions.
15
- - ðŸ“Ķ **Fluent Record CRUD & Batch Pagination**: `getList`, `getFullList`, `getFirstListItem`, `create`, `update`, `delete`, `export`, and `import`.
16
- - 🔍 **SQLite FTS5 Full-Text Search**: Embedded BM25 relevance ranking, keyword prefix matching, and contextual snippet highlighting.
17
- - 🔔 **Outbound Webhooks Service**: Programmatic webhook registration, test dispatch, delivery inspection, and HMAC-SHA256 signature verification (`client.webhooks`).
18
- - 📁 **File Uploads & Protected File URLs**: Seamless `FormData` file uploads and URL generation with automatic token appending.
19
- - ðŸ“Ą **Multiplexed Realtime SSE**: Single persistent connection with auto-handshake (`NS_CONNECT`), dynamic subscription sync, and automatic reconnection.
20
- - ðŸŽŊ **100% Type Safety**: Works seamlessly with `nodestack typegen` and `GET /api/types.d.ts` for autocompleted collection names and typed record returns.
21
- - 🌐 **Isomorphic**: Modern Browsers, React / Next.js (SSR + Client), Vue, Svelte, Node.js (18+ / 22+), Bun, Deno, and React Native.
22
-
23
- ---
24
-
25
- ## ðŸ“Ķ Installation
26
-
27
- ```bash
28
- npm install nodestack-client
29
- ```
30
-
31
- Or using Yarn / PNPM / Bun:
32
- ```bash
33
- pnpm add nodestack-client
34
- # or
35
- bun add nodestack-client
36
- ```
37
-
38
- ---
39
-
40
- ## ⚡ Quick Start
41
-
42
- ```typescript
43
- import { NodeStackClient } from 'nodestack-client';
44
-
45
- // Initialize client
46
- const client = new NodeStackClient('http://127.0.0.1:8090');
47
-
48
- // Fetch paginated records
49
- const result = await client.collection('posts').getList(1, 20, {
50
- filter: "status = 'published' && views >= 10",
51
- sort: '-created',
52
- });
53
-
54
- console.log(`Found ${result.totalItems} posts:`, result.items);
55
- ```
56
-
57
- ---
58
-
59
- ## 🔐 Authentication
60
-
61
- ### Authenticate as an Auth Collection User (e.g. `users`)
62
- ```typescript
63
- const authData = await client.collection('users').authWithPassword('alice@example.com', 'password123');
64
-
65
- console.log('Logged in user:', client.authStore.model);
66
- console.log('JWT Token:', client.authStore.token);
67
- console.log('Is valid:', client.authStore.isValid);
68
- ```
69
-
70
- ### Authenticate as a Superuser / Admin
71
- ```typescript
72
- const adminAuth = await client.admins.authWithPassword('admin@nodestack.io', 'MySecretAdminPassword');
73
- console.log('Logged in superuser:', adminAuth.admin.email);
74
- ```
75
-
76
- ### Reactive Auth State Changes
77
- ```typescript
78
- // Subscribe to login / logout events (ideal for React, Vue, Svelte stores)
79
- const unbind = client.authStore.onChange((token, model) => {
80
- console.log('Auth state changed:', { token, model });
81
- });
82
-
83
- // Logout
84
- client.authStore.clear();
85
-
86
- // Unsubscribe listener
87
- unbind();
88
- ```
89
-
90
- ### SSR & Node.js Environments (`MemoryAuthStore`)
91
- In Node.js scripts or server-side rendering where `window.localStorage` is unavailable:
92
- ```typescript
93
- import { NodeStackClient, MemoryAuthStore } from 'nodestack-client';
94
-
95
- const serverClient = new NodeStackClient('http://127.0.0.1:8090', {
96
- authStore: new MemoryAuthStore(),
97
- });
98
- ```
99
-
100
- ---
101
-
102
- ## 📋 Record Operations (CRUD)
103
-
104
- ### 1. Fetch Paginated Records (`getList`)
105
- ```typescript
106
- const result = await client.collection('posts').getList(1, 10, {
107
- filter: "category = 'Tech' && price <= 50",
108
- sort: '-created,title',
109
- expand: 'author,comments',
110
- });
111
-
112
- console.log(result.page, result.totalPages, result.items);
113
- ```
114
-
115
- ### 2. Full-Text Search with BM25 Scoring & Snippets (`search`)
116
- Query records using embedded SQLite FTS5 with BM25 relevance ranking and keyword highlight snippets:
117
- ```typescript
118
- const searchResults = await client.collection('posts').getList(1, 10, {
119
- search: 'distributed systems',
120
- snippetTag: 'mark', // Highlight tag (default: <mark>)
121
- snippetLength: 25, // Max words per snippet excerpt
122
- });
123
-
124
- console.log(searchResults.items[0]._score); // BM25 relevance score
125
- console.log(searchResults.items[0]._snippet); // Best matching snippet excerpt
126
- console.log(searchResults.items[0]._snippets); // Per-field snippet map: { title: "...", content: "..." }
127
- ```
128
-
129
- ### 3. Fetch All Records Across Pages (`getFullList`)
130
- Auto-paginates in batches and returns all matching records as a single array:
131
- ```typescript
132
- const allPosts = await client.collection('posts').getFullList({
133
- batch: 200,
134
- filter: "published = true",
135
- });
136
- ```
137
-
138
- ### 4. Find First Matching Record (`getFirstListItem`)
139
- ```typescript
140
- // Throws ClientResponseError(404) if no match is found
141
- const post = await client.collection('posts').getFirstListItem("slug = 'hello-world'");
142
- ```
143
-
144
- ### 5. Create Record (JSON or File Uploads)
145
- ```typescript
146
- // Plain JSON
147
- const newPost = await client.collection('posts').create({
148
- title: 'My First Post',
149
- published: true,
150
- });
151
-
152
- // Multipart Form Data (File Upload)
153
- const formData = new FormData();
154
- formData.append('title', 'Post with Cover Photo');
155
- formData.append('coverImage', fileInput.files[0]);
156
-
157
- const postWithImage = await client.collection('posts').create(formData);
158
- ```
159
-
160
- ### 6. Update & Delete Records
161
- ```typescript
162
- // Update
163
- const updated = await client.collection('posts').update('rec_123', {
164
- title: 'Updated Title',
165
- });
166
-
167
- // Delete
168
- await client.collection('posts').delete('rec_123');
169
- ```
170
-
171
- ---
172
-
173
- ## 📁 File URLs Helper
174
-
175
- NodeStack provides protected and public file serving at `/api/files/:collection/:recordId/:filename`. Construct fully-qualified URLs easily:
176
-
177
- ```typescript
178
- const record = await client.collection('posts').getOne('rec_123');
179
-
180
- // 1. Standard URL for <img> tags
181
- const imageUrl = client.files.getUrl(record, record.coverImage);
182
-
183
- // 2. Trigger browser download dialog
184
- const downloadUrl = client.files.getUrl(record, record.attachment, {
185
- download: true,
186
- });
187
-
188
- // 3. Attach authentication token for protected files
189
- const protectedUrl = client.files.getUrl(record, record.secretDoc, {
190
- token: true, // Uses active authStore token
191
- });
192
- ```
193
-
194
- ---
195
-
196
- ## ðŸ“Ą Realtime Subscriptions (SSE)
197
-
198
- NodeStack uses Server-Sent Events (SSE) with an initial `NS_CONNECT` handshake and multiplexed collection channels. `nodestack-client` manages this with automatic reconnection and topic synchronization:
199
-
200
- ```typescript
201
- // 1. Subscribe to all changes in a collection
202
- const unsubscribe = await client.collection('posts').subscribe('*', (event) => {
203
- console.log(`Action: ${event.action}`); // 'create' | 'update' | 'delete'
204
- console.log('Record:', event.record);
205
- });
206
-
207
- // 2. Subscribe to a specific record by ID
208
- const unbindRecord = await client.collection('posts').subscribe('rec_123', (event) => {
209
- console.log('Record updated:', event.record);
210
- });
211
-
212
- // 3. Clean up subscriptions when unmounting
213
- await unsubscribe();
214
- await unbindRecord();
215
- ```
216
-
217
- ---
218
-
219
- ## ðŸŽŊ End-to-End Type Safety
220
-
221
- Pair `nodestack-client` with `nodestack typegen`:
222
-
223
- ```bash
224
- # Generate TypeScript definitions from your running server
225
- npx nodestack typegen > src/types/nodestack.ts
226
- ```
227
-
228
- Then supply your `SchemaCollections` dictionary to `NodeStackClient`:
229
-
230
- ```typescript
231
- import { NodeStackClient } from 'nodestack-client';
232
- import { SchemaCollections } from './types/nodestack';
233
-
234
- const client = new NodeStackClient<SchemaCollections>('http://127.0.0.1:8090');
235
-
236
- // Autocomplete for collection names: 'posts' | 'users' | 'orders'
237
- // Return types are automatically inferred as PostRecord, UserRecord, etc.
238
- const posts = await client.collection('posts').getList();
239
- posts.items[0].title; // Fully typed!
240
- ```
241
-
242
- ---
243
-
244
- ## ðŸ›Ąïļ Error Handling
245
-
246
- When NodeStack returns an error (4xx or 5xx), a `ClientResponseError` is thrown:
247
-
248
- ```typescript
249
- import { ClientResponseError } from 'nodestack-client';
250
-
251
- try {
252
- await client.collection('posts').getOne('missing_id');
253
- } catch (err) {
254
- if (err instanceof ClientResponseError) {
255
- console.error('Status:', err.status); // e.g. 404
256
- console.error('Message:', err.message); // e.g. "Record not found"
257
- console.error('Data:', err.data); // Raw response JSON
258
- console.error('Is Aborted:', err.isAbort);
259
- }
260
- }
261
- ```
262
-
263
- ---
264
-
265
- ## ⚡ Latency & Chaos Simulation (For Testing Frontend States)
266
-
267
- Test your frontend loading spinners, skeleton states, error toasts, and retry mechanisms against simulated network conditions:
268
-
269
- ```typescript
270
- // 1. Query-level simulation
271
- const posts = await client.collection('posts').getList({
272
- mock_delay: 1500, // 1.5s mobile 3G delay to test loading skeletons
273
- });
274
-
275
- // 2. Simulate network drops & HTTP errors
276
- try {
277
- await client.collection('posts').getList({
278
- mock_error: 500, // Or ?mock_fail_rate=0.2 for random 20% drops
279
- });
280
- } catch (err) {
281
- if (err instanceof ClientResponseError) {
282
- showToast(`Server error (${err.status}): ${err.message}`);
283
- }
284
- }
285
-
286
- // 3. App-wide chaos simulation toggle (useful during frontend development)
287
- client.setChaos({
288
- delay: 1000, // 1000ms delay on all requests
289
- failRate: 0.15, // 15% random network drop rate
290
- errorStatus: 503 // Return 503 on failure
291
- });
292
-
293
- // Disable chaos
294
- client.setChaos(null);
295
- ```
296
-
297
- ---
298
-
299
- ## 🔔 Outbound Webhooks Management & Signature Verification
300
-
301
- Manage outbound webhook subscriptions, inspect delivery logs, and cryptographically verify incoming webhook payloads:
302
-
303
- ```typescript
304
- // 1. Create a webhook subscription
305
- const webhook = await client.webhooks.create({
306
- name: 'Slack Order Dispatcher',
307
- url: 'https://api.myapp.com/webhooks/orders',
308
- collection: 'orders',
309
- action: 'create', // 'create' | 'update' | 'delete' | '*'
310
- secret: 'whsec_9948a8cf38b1e42c',
311
- enabled: true,
312
- });
313
-
314
- // 2. Dispatch an instant test payload
315
- await client.webhooks.test(webhook.id, { test: true });
316
-
317
- // 3. Inspect delivery history logs
318
- const deliveries = await client.webhooks.getDeliveries({
319
- webhookId: webhook.id,
320
- status: 'failed',
321
- });
322
-
323
- // 4. Retry a failed delivery attempt
324
- if (deliveries.items.length > 0) {
325
- await client.webhooks.retryDelivery(deliveries.items[0].id);
326
- }
327
-
328
- // 5. Verify HMAC-SHA256 signature in your receiving server/microservice
329
- const isValid = client.webhooks.verifySignature(
330
- rawRequestBodyString,
331
- requestHeaders['x-nodestack-signature'],
332
- 'whsec_9948a8cf38b1e42c'
333
- );
334
- ```
335
-
336
- ---
337
-
338
- ## 📄 License
339
-
340
- MIT ÂĐ NodeStack Contributors
341
-
1
+ # 🚀 nodestack-client
2
+
3
+ > **The Official Lightweight, Type-Safe JavaScript & TypeScript Client SDK for [NodeStack](https://github.com/satya-jugran/nodestack)**
4
+ >
5
+ > Published on npm: [`nodestack-client`](https://www.npmjs.com/package/nodestack-client)
6
+
7
+ [![npm version](https://img.shields.io/npm/v/nodestack-client.svg?color=blue)](https://www.npmjs.com/package/nodestack-client)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-7.x-blue.svg)](https://www.typescriptlang.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+
11
+ A zero-dependency, isomorphic JavaScript and TypeScript client for NodeStack with out-of-the-box support for:
12
+ - ⚡ **Zero runtime dependencies**: Built purely on standard Web APIs (`fetch`, `FormData`, `AbortController`, `EventSource`).
13
+ - ðŸŠķ **Ultra-lightweight footprint**: `< 10 KB` minified + gzipped.
14
+ - ðŸĒ **Multi-Application & App Scoping (v2.0)**: Fluent `client.app(slug)` scoping with automatic `x-nodestack-app` header injection, scoped SSE streams, and administrative `client.apps` management.
15
+ - 🔒 **Pluggable & Reactive Auth**: `LocalAuthStore` (localStorage), `MemoryAuthStore` (Node/SSR/testing), and reactive `onChange` state subscriptions.
16
+ - ðŸ“Ķ **Fluent Record CRUD & Batch Pagination**: `getList`, `getFullList`, `getFirstListItem`, `create`, `update`, `delete`, `export`, and `import`.
17
+ - 🔍 **SQLite FTS5 Full-Text Search**: Embedded BM25 relevance ranking, keyword prefix matching, and contextual snippet highlighting.
18
+ - 🔔 **Outbound Webhooks Service**: Programmatic webhook registration, test dispatch, delivery inspection, and HMAC-SHA256 signature verification (`client.webhooks`).
19
+ - 📁 **File Uploads & Protected File URLs**: Seamless `FormData` file uploads and URL generation with automatic token appending.
20
+ - ðŸ“Ą **Multiplexed Realtime SSE**: Single persistent connection with auto-handshake (`NS_CONNECT`), dynamic subscription sync, and automatic reconnection.
21
+ - ðŸŽŊ **100% Type Safety**: Works seamlessly with `nodestack typegen` and `GET /api/types.d.ts` for autocompleted collection names and typed record returns.
22
+ - 🌐 **Isomorphic**: Modern Browsers, React / Next.js (SSR + Client), Vue, Svelte, Node.js (20+ / 22+ / 24+), Bun, Deno, and React Native.
23
+
24
+ ---
25
+
26
+ ## ðŸ“Ķ Installation
27
+
28
+ ```bash
29
+ npm install nodestack-client
30
+ ```
31
+
32
+ Or using Yarn / PNPM / Bun:
33
+ ```bash
34
+ pnpm add nodestack-client
35
+ # or
36
+ bun add nodestack-client
37
+ ```
38
+
39
+ ---
40
+
41
+ ## ⚡ Quick Start
42
+
43
+ ```typescript
44
+ import { NodeStackClient } from 'nodestack-client';
45
+
46
+ // Initialize client
47
+ const client = new NodeStackClient('http://127.0.0.1:8090');
48
+
49
+ // Fetch paginated records
50
+ const result = await client.collection('posts').getList(1, 20, {
51
+ filter: "status = 'published' && views >= 10",
52
+ sort: '-created',
53
+ });
54
+
55
+ console.log(`Found ${result.totalItems} posts:`, result.items);
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 🔐 Authentication
61
+
62
+ ### Authenticate as an Auth Collection User (e.g. `users`)
63
+ ```typescript
64
+ const authData = await client.collection('users').authWithPassword('alice@example.com', 'password123');
65
+
66
+ console.log('Logged in user:', client.authStore.model);
67
+ console.log('JWT Token:', client.authStore.token);
68
+ console.log('Is valid:', client.authStore.isValid);
69
+ ```
70
+
71
+ ### Authenticate via OAuth2 / Social Login (PKCE)
72
+ NodeStack supports native PKCE code exchange for Google, GitHub, Apple, Discord, and Microsoft:
73
+
74
+ ```typescript
75
+ // 1. Fetch available providers and precomputed PKCE challenge & state
76
+ const { authProviders } = await client.collection('users').listAuthMethods('http://localhost:3000/callback');
77
+ const google = authProviders.find((p) => p.name === 'google');
78
+
79
+ // 2. Redirect user or open popup with google.authUrl
80
+ // window.location.href = google.authUrl;
81
+
82
+ // 3. On your callback page (/callback), exchange code and codeVerifier
83
+ const urlParams = new URLSearchParams(window.location.search);
84
+ const code = urlParams.get('code');
85
+
86
+ const authData = await client.collection('users').authWithOAuth2({
87
+ provider: 'google',
88
+ code: code!,
89
+ codeVerifier: google.codeVerifier,
90
+ state: google.state,
91
+ redirectUrl: 'http://localhost:3000/callback',
92
+ });
93
+
94
+ console.log('Logged in via Google:', authData.record);
95
+ console.log('Is newly registered user:', authData.meta.isNew);
96
+ ```
97
+
98
+ #### One-Step Popup Authentication (Single-Page Apps)
99
+ ```typescript
100
+ // nodestack-client verifies popup origin, window source, and binds CSRF state automatically:
101
+ const authData = await client.collection('users').authWithOAuth2({
102
+ provider: 'google',
103
+ urlCallback: (url) => window.open(url, '_blank', 'width=500,height=650'),
104
+ redirectUrl: 'http://localhost:3000/callback',
105
+ });
106
+ ```
107
+
108
+ ### Machine-to-Machine (M2M) Authentication with API Keys
109
+ For background workers, cron daemons, serverless scripts, and CI/CD pipelines:
110
+
111
+ ```typescript
112
+ import { NodeStackClient } from 'nodestack-client';
113
+
114
+ // Initialized with a scoped API key (e.g. "orders:*", "products:read")
115
+ const client = new NodeStackClient('https://api.yourdomain.com', {
116
+ apiKey: process.env.NODESTACK_API_KEY, // 'nsk_live_...'
117
+ });
118
+
119
+ // Requests automatically inject Authorization: Bearer nsk_...
120
+ const orders = await client.collection('orders').getList(1, 20);
121
+ ```
122
+
123
+ ### Authenticate as a Superuser / Admin
124
+ ```typescript
125
+ const adminAuth = await client.admins.authWithPassword('admin@nodestack.io', 'MySecretAdminPassword');
126
+ console.log('Logged in superuser:', adminAuth.admin.email);
127
+ ```
128
+
129
+ ### Reactive Auth State Changes
130
+ ```typescript
131
+ // Subscribe to login / logout events (ideal for React, Vue, Svelte stores)
132
+ const unbind = client.authStore.onChange((token, model) => {
133
+ console.log('Auth state changed:', { token, model });
134
+ });
135
+
136
+ // Logout
137
+ client.authStore.clear();
138
+
139
+ // Unsubscribe listener
140
+ unbind();
141
+ ```
142
+
143
+ ### SSR & Node.js Environments (`MemoryAuthStore`)
144
+ In Node.js scripts or server-side rendering where `window.localStorage` is unavailable:
145
+ ```typescript
146
+ import { NodeStackClient, MemoryAuthStore } from 'nodestack-client';
147
+
148
+ const serverClient = new NodeStackClient('http://127.0.0.1:8090', {
149
+ authStore: new MemoryAuthStore(),
150
+ });
151
+ ```
152
+
153
+ ---
154
+
155
+ ## ðŸĒ Multi-Application & App Scoping (v2.0)
156
+
157
+ NodeStack v2.0 supports hosting multiple isolated applications on a single backend instance. `nodestack-client` makes targeting and managing applications seamless:
158
+
159
+ ### 1. Scoped Client Instances (`client.app`)
160
+ Call `client.app(slug)` to create a scoped client. It shares the same connection config and auth store (or a custom one) while automatically scoping all requests:
161
+ - Automatically injects `x-nodestack-app: slug` on all collection requests.
162
+ - Scopes Realtime SSE streams (`/api/realtime?app=slug`).
163
+ - Generates scoped file URLs (`/api/apps/:appId/files/...`).
164
+
165
+ ```typescript
166
+ const client = new NodeStackClient('http://127.0.0.1:8090');
167
+
168
+ // Scope to the 'ecommerce' application
169
+ const shopClient = client.app('ecommerce');
170
+
171
+ // All queries now operate within 'ecommerce' SQLite database
172
+ const products = await shopClient.collection('products').getList(1, 20);
173
+
174
+ // File URLs automatically point to the app-scoped file route:
175
+ // http://127.0.0.1:8090/api/apps/ecommerce/files/products/rec_123/image.png
176
+ const imgUrl = shopClient.files.getUrl(products.items[0], products.items[0].cover);
177
+
178
+ // Realtime SSE scoped to 'ecommerce'
179
+ shopClient.collection('orders').subscribe('*', (e) => {
180
+ console.log('New shop order:', e.record);
181
+ });
182
+ ```
183
+
184
+ ### 2. Multi-App Management Service (`client.apps`)
185
+ Administrative scripts or superusers can provision, inspect, and delete applications programmatically:
186
+
187
+ ```typescript
188
+ // Authenticate as superuser
189
+ await client.admins.authWithPassword('admin@nodestack.io', 'AdminPass123!');
190
+
191
+ // List hosted applications
192
+ const apps = await client.apps.getList(1, 50);
193
+
194
+ // Create a new app from a starter template ('saas' | 'blog' | 'ecommerce' | 'blank')
195
+ const newApp = await client.apps.create({
196
+ name: 'Analytics Dashboard',
197
+ slug: 'analytics',
198
+ template: 'saas',
199
+ });
200
+
201
+ // Update app details
202
+ await client.apps.update(newApp.id, {
203
+ name: 'Analytics Production',
204
+ });
205
+
206
+ // Delete an application (deletes its SQLite DB & uploaded files)
207
+ await client.apps.delete(newApp.id);
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 📋 Record Operations (CRUD)
213
+
214
+ ### 1. Fetch Paginated Records (`getList`)
215
+ ```typescript
216
+ const result = await client.collection('posts').getList(1, 10, {
217
+ filter: "category = 'Tech' && price <= 50",
218
+ sort: '-created,title',
219
+ expand: 'author,comments',
220
+ });
221
+
222
+ console.log(result.page, result.totalPages, result.items);
223
+ ```
224
+
225
+ ### 2. Full-Text Search with BM25 Scoring & Snippets (`search`)
226
+ Query records using embedded SQLite FTS5 with BM25 relevance ranking and keyword highlight snippets:
227
+ ```typescript
228
+ const searchResults = await client.collection('posts').getList(1, 10, {
229
+ search: 'distributed systems',
230
+ snippetTag: 'mark', // Highlight tag (default: <mark>)
231
+ snippetLength: 25, // Max words per snippet excerpt
232
+ });
233
+
234
+ console.log(searchResults.items[0]._score); // BM25 relevance score
235
+ console.log(searchResults.items[0]._snippet); // Best matching snippet excerpt
236
+ console.log(searchResults.items[0]._snippets); // Per-field snippet map: { title: "...", content: "..." }
237
+ ```
238
+
239
+ ### 3. Fetch All Records Across Pages (`getFullList`)
240
+ Auto-paginates in batches and returns all matching records as a single array:
241
+ ```typescript
242
+ const allPosts = await client.collection('posts').getFullList({
243
+ batch: 200,
244
+ filter: "published = true",
245
+ });
246
+ ```
247
+
248
+ ### 4. Find First Matching Record (`getFirstListItem`)
249
+ ```typescript
250
+ // Throws ClientResponseError(404) if no match is found
251
+ const post = await client.collection('posts').getFirstListItem("slug = 'hello-world'");
252
+ ```
253
+
254
+ ### 5. Create Record (JSON or File Uploads)
255
+ ```typescript
256
+ // Plain JSON
257
+ const newPost = await client.collection('posts').create({
258
+ title: 'My First Post',
259
+ published: true,
260
+ });
261
+
262
+ // Multipart Form Data (File Upload)
263
+ const formData = new FormData();
264
+ formData.append('title', 'Post with Cover Photo');
265
+ formData.append('coverImage', fileInput.files[0]);
266
+
267
+ const postWithImage = await client.collection('posts').create(formData);
268
+ ```
269
+
270
+ ### 6. Update & Delete Records
271
+ ```typescript
272
+ // Update
273
+ const updated = await client.collection('posts').update('rec_123', {
274
+ title: 'Updated Title',
275
+ });
276
+
277
+ // Delete
278
+ await client.collection('posts').delete('rec_123');
279
+ ```
280
+
281
+ ---
282
+
283
+ ## 📁 File URLs Helper
284
+
285
+ NodeStack provides protected and public file serving at `/api/files/:collection/:recordId/:filename`. Construct fully-qualified URLs easily:
286
+
287
+ ```typescript
288
+ const record = await client.collection('posts').getOne('rec_123');
289
+
290
+ // 1. Standard URL for <img> tags
291
+ const imageUrl = client.files.getUrl(record, record.coverImage);
292
+
293
+ // 2. Trigger browser download dialog
294
+ const downloadUrl = client.files.getUrl(record, record.attachment, {
295
+ download: true,
296
+ });
297
+
298
+ // 3. Attach authentication token for protected files
299
+ const protectedUrl = client.files.getUrl(record, record.secretDoc, {
300
+ token: true, // Uses active authStore token
301
+ });
302
+ ```
303
+
304
+ ---
305
+
306
+ ## ðŸ“Ą Realtime Subscriptions (SSE)
307
+
308
+ NodeStack uses Server-Sent Events (SSE) with an initial `NS_CONNECT` handshake and multiplexed collection channels. `nodestack-client` manages this with automatic reconnection and topic synchronization:
309
+
310
+ ```typescript
311
+ // 1. Subscribe to all changes in a collection
312
+ const unsubscribe = await client.collection('posts').subscribe('*', (event) => {
313
+ console.log(`Action: ${event.action}`); // 'create' | 'update' | 'delete'
314
+ console.log('Record:', event.record);
315
+ });
316
+
317
+ // 2. Subscribe to a specific record by ID
318
+ const unbindRecord = await client.collection('posts').subscribe('rec_123', (event) => {
319
+ console.log('Record updated:', event.record);
320
+ });
321
+
322
+ // 3. Clean up subscriptions when unmounting
323
+ await unsubscribe();
324
+ await unbindRecord();
325
+ ```
326
+
327
+ ---
328
+
329
+ ## ðŸŽŊ End-to-End Type Safety
330
+
331
+ Pair `nodestack-client` with `nodestack typegen`:
332
+
333
+ ```bash
334
+ # Generate TypeScript definitions from your running server
335
+ npx nodestack typegen > src/types/nodestack.ts
336
+ ```
337
+
338
+ Then supply your `SchemaCollections` dictionary to `NodeStackClient`:
339
+
340
+ ```typescript
341
+ import { NodeStackClient } from 'nodestack-client';
342
+ import { SchemaCollections } from './types/nodestack';
343
+
344
+ const client = new NodeStackClient<SchemaCollections>('http://127.0.0.1:8090');
345
+
346
+ // Autocomplete for collection names: 'posts' | 'users' | 'orders'
347
+ // Return types are automatically inferred as PostRecord, UserRecord, etc.
348
+ const posts = await client.collection('posts').getList();
349
+ posts.items[0].title; // Fully typed!
350
+ ```
351
+
352
+ ---
353
+
354
+ ## ðŸ›Ąïļ Error Handling
355
+
356
+ When NodeStack returns an error (4xx or 5xx), a `ClientResponseError` is thrown:
357
+
358
+ ```typescript
359
+ import { ClientResponseError } from 'nodestack-client';
360
+
361
+ try {
362
+ await client.collection('posts').getOne('missing_id');
363
+ } catch (err) {
364
+ if (err instanceof ClientResponseError) {
365
+ console.error('Status:', err.status); // e.g. 404
366
+ console.error('Message:', err.message); // e.g. "Record not found"
367
+ console.error('Data:', err.data); // Raw response JSON
368
+ console.error('Is Aborted:', err.isAbort);
369
+ }
370
+ }
371
+ ```
372
+
373
+ ---
374
+
375
+ ## ⚡ Latency & Chaos Simulation (For Testing Frontend States)
376
+
377
+ Test your frontend loading spinners, skeleton states, error toasts, and retry mechanisms against simulated network conditions:
378
+
379
+ ```typescript
380
+ // 1. Query-level simulation
381
+ const posts = await client.collection('posts').getList({
382
+ mock_delay: 1500, // 1.5s mobile 3G delay to test loading skeletons
383
+ });
384
+
385
+ // 2. Simulate network drops & HTTP errors
386
+ try {
387
+ await client.collection('posts').getList({
388
+ mock_error: 500, // Or ?mock_fail_rate=0.2 for random 20% drops
389
+ });
390
+ } catch (err) {
391
+ if (err instanceof ClientResponseError) {
392
+ showToast(`Server error (${err.status}): ${err.message}`);
393
+ }
394
+ }
395
+
396
+ // 3. App-wide chaos simulation toggle (useful during frontend development)
397
+ client.setChaos({
398
+ delay: 1000, // 1000ms delay on all requests
399
+ failRate: 0.15, // 15% random network drop rate
400
+ errorStatus: 503 // Return 503 on failure
401
+ });
402
+
403
+ // Disable chaos
404
+ client.setChaos(null);
405
+ ```
406
+
407
+ ---
408
+
409
+ ## 🔔 Outbound Webhooks Management & Signature Verification
410
+
411
+ Manage outbound webhook subscriptions, inspect delivery logs, and cryptographically verify incoming webhook payloads:
412
+
413
+ ```typescript
414
+ // 1. Create a webhook subscription
415
+ const webhook = await client.webhooks.create({
416
+ name: 'Slack Order Dispatcher',
417
+ url: 'https://api.myapp.com/webhooks/orders',
418
+ collection: 'orders',
419
+ action: 'create', // 'create' | 'update' | 'delete' | '*'
420
+ secret: 'whsec_9948a8cf38b1e42c',
421
+ enabled: true,
422
+ });
423
+
424
+ // 2. Dispatch an instant test payload
425
+ await client.webhooks.test(webhook.id, { test: true });
426
+
427
+ // 3. Inspect delivery history logs
428
+ const deliveries = await client.webhooks.getDeliveries({
429
+ webhookId: webhook.id,
430
+ status: 'failed',
431
+ });
432
+
433
+ // 4. Retry a failed delivery attempt
434
+ if (deliveries.items.length > 0) {
435
+ await client.webhooks.retryDelivery(deliveries.items[0].id);
436
+ }
437
+
438
+ // 5. Verify HMAC-SHA256 signature in your receiving server/microservice
439
+ const isValid = client.webhooks.verifySignature(
440
+ rawRequestBodyString,
441
+ requestHeaders['x-nodestack-signature'],
442
+ 'whsec_9948a8cf38b1e42c'
443
+ );
444
+ ```
445
+
446
+ ---
447
+
448
+ ## 📄 License
449
+
450
+ MIT ÂĐ NodeStack Contributors
451
+