nodestack-client 1.2.0 → 2.0.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,22 +1,25 @@
1
1
  # 🚀 nodestack-client
2
2
 
3
- > **The Official Lightweight, Type-Safe JavaScript & TypeScript Client SDK for [NodeStack](https://github.com/satya-jugran/nodebase)**
3
+ > **The Official Lightweight, Type-Safe JavaScript & TypeScript Client SDK for [NodeStack](https://github.com/satya-jugran/nodestack)**
4
4
  >
5
5
  > Published on npm: [`nodestack-client`](https://www.npmjs.com/package/nodestack-client)
6
6
 
7
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/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-7.x-blue.svg)](https://www.typescriptlang.org/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
 
11
11
  A zero-dependency, isomorphic JavaScript and TypeScript client for NodeStack with out-of-the-box support for:
12
12
  - ⚡ **Zero runtime dependencies**: Built purely on standard Web APIs (`fetch`, `FormData`, `AbortController`, `EventSource`).
13
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.
14
15
  - 🔒 **Pluggable & Reactive Auth**: `LocalAuthStore` (localStorage), `MemoryAuthStore` (Node/SSR/testing), and reactive `onChange` state subscriptions.
15
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`).
16
19
  - 📁 **File Uploads & Protected File URLs**: Seamless `FormData` file uploads and URL generation with automatic token appending.
17
20
  - ðŸ“Ą **Multiplexed Realtime SSE**: Single persistent connection with auto-handshake (`NS_CONNECT`), dynamic subscription sync, and automatic reconnection.
18
21
  - ðŸŽŊ **100% Type Safety**: Works seamlessly with `nodestack typegen` and `GET /api/types.d.ts` for autocompleted collection names and typed record returns.
19
- - 🌐 **Isomorphic**: Modern Browsers, React / Next.js (SSR + Client), Vue, Svelte, Node.js (18+ / 22+), Bun, Deno, and React Native.
22
+ - 🌐 **Isomorphic**: Modern Browsers, React / Next.js (SSR + Client), Vue, Svelte, Node.js (20+ / 22+ / 24+), Bun, Deno, and React Native.
20
23
 
21
24
  ---
22
25
 
@@ -97,6 +100,63 @@ const serverClient = new NodeStackClient('http://127.0.0.1:8090', {
97
100
 
98
101
  ---
99
102
 
103
+ ## ðŸĒ Multi-Application & App Scoping (v2.0)
104
+
105
+ NodeStack v2.0 supports hosting multiple isolated applications on a single backend instance. `nodestack-client` makes targeting and managing applications seamless:
106
+
107
+ ### 1. Scoped Client Instances (`client.app`)
108
+ 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:
109
+ - Automatically injects `x-nodestack-app: slug` on all collection requests.
110
+ - Scopes Realtime SSE streams (`/api/realtime?app=slug`).
111
+ - Generates scoped file URLs (`/api/apps/:appId/files/...`).
112
+
113
+ ```typescript
114
+ const client = new NodeStackClient('http://127.0.0.1:8090');
115
+
116
+ // Scope to the 'ecommerce' application
117
+ const shopClient = client.app('ecommerce');
118
+
119
+ // All queries now operate within 'ecommerce' SQLite database
120
+ const products = await shopClient.collection('products').getList(1, 20);
121
+
122
+ // File URLs automatically point to the app-scoped file route:
123
+ // http://127.0.0.1:8090/api/apps/ecommerce/files/products/rec_123/image.png
124
+ const imgUrl = shopClient.files.getUrl(products.items[0], products.items[0].cover);
125
+
126
+ // Realtime SSE scoped to 'ecommerce'
127
+ shopClient.collection('orders').subscribe('*', (e) => {
128
+ console.log('New shop order:', e.record);
129
+ });
130
+ ```
131
+
132
+ ### 2. Multi-App Management Service (`client.apps`)
133
+ Administrative scripts or superusers can provision, inspect, and delete applications programmatically:
134
+
135
+ ```typescript
136
+ // Authenticate as superuser
137
+ await client.admins.authWithPassword('admin@nodestack.io', 'AdminPass123!');
138
+
139
+ // List hosted applications
140
+ const apps = await client.apps.getList(1, 50);
141
+
142
+ // Create a new app from a starter template ('saas' | 'blog' | 'ecommerce' | 'blank')
143
+ const newApp = await client.apps.create({
144
+ name: 'Analytics Dashboard',
145
+ slug: 'analytics',
146
+ template: 'saas',
147
+ });
148
+
149
+ // Update app details
150
+ await client.apps.update(newApp.id, {
151
+ name: 'Analytics Production',
152
+ });
153
+
154
+ // Delete an application (deletes its SQLite DB & uploaded files)
155
+ await client.apps.delete(newApp.id);
156
+ ```
157
+
158
+ ---
159
+
100
160
  ## 📋 Record Operations (CRUD)
101
161
 
102
162
  ### 1. Fetch Paginated Records (`getList`)
@@ -110,7 +170,21 @@ const result = await client.collection('posts').getList(1, 10, {
110
170
  console.log(result.page, result.totalPages, result.items);
111
171
  ```
112
172
 
113
- ### 2. Fetch All Records Across Pages (`getFullList`)
173
+ ### 2. Full-Text Search with BM25 Scoring & Snippets (`search`)
174
+ Query records using embedded SQLite FTS5 with BM25 relevance ranking and keyword highlight snippets:
175
+ ```typescript
176
+ const searchResults = await client.collection('posts').getList(1, 10, {
177
+ search: 'distributed systems',
178
+ snippetTag: 'mark', // Highlight tag (default: <mark>)
179
+ snippetLength: 25, // Max words per snippet excerpt
180
+ });
181
+
182
+ console.log(searchResults.items[0]._score); // BM25 relevance score
183
+ console.log(searchResults.items[0]._snippet); // Best matching snippet excerpt
184
+ console.log(searchResults.items[0]._snippets); // Per-field snippet map: { title: "...", content: "..." }
185
+ ```
186
+
187
+ ### 3. Fetch All Records Across Pages (`getFullList`)
114
188
  Auto-paginates in batches and returns all matching records as a single array:
115
189
  ```typescript
116
190
  const allPosts = await client.collection('posts').getFullList({
@@ -119,13 +193,13 @@ const allPosts = await client.collection('posts').getFullList({
119
193
  });
120
194
  ```
121
195
 
122
- ### 3. Find First Matching Record (`getFirstListItem`)
196
+ ### 4. Find First Matching Record (`getFirstListItem`)
123
197
  ```typescript
124
198
  // Throws ClientResponseError(404) if no match is found
125
199
  const post = await client.collection('posts').getFirstListItem("slug = 'hello-world'");
126
200
  ```
127
201
 
128
- ### 4. Create Record (JSON or File Uploads)
202
+ ### 5. Create Record (JSON or File Uploads)
129
203
  ```typescript
130
204
  // Plain JSON
131
205
  const newPost = await client.collection('posts').create({
@@ -141,7 +215,7 @@ formData.append('coverImage', fileInput.files[0]);
141
215
  const postWithImage = await client.collection('posts').create(formData);
142
216
  ```
143
217
 
144
- ### 5. Update & Delete Records
218
+ ### 6. Update & Delete Records
145
219
  ```typescript
146
220
  // Update
147
221
  const updated = await client.collection('posts').update('rec_123', {
@@ -280,6 +354,45 @@ client.setChaos(null);
280
354
 
281
355
  ---
282
356
 
357
+ ## 🔔 Outbound Webhooks Management & Signature Verification
358
+
359
+ Manage outbound webhook subscriptions, inspect delivery logs, and cryptographically verify incoming webhook payloads:
360
+
361
+ ```typescript
362
+ // 1. Create a webhook subscription
363
+ const webhook = await client.webhooks.create({
364
+ name: 'Slack Order Dispatcher',
365
+ url: 'https://api.myapp.com/webhooks/orders',
366
+ collection: 'orders',
367
+ action: 'create', // 'create' | 'update' | 'delete' | '*'
368
+ secret: 'whsec_9948a8cf38b1e42c',
369
+ enabled: true,
370
+ });
371
+
372
+ // 2. Dispatch an instant test payload
373
+ await client.webhooks.test(webhook.id, { test: true });
374
+
375
+ // 3. Inspect delivery history logs
376
+ const deliveries = await client.webhooks.getDeliveries({
377
+ webhookId: webhook.id,
378
+ status: 'failed',
379
+ });
380
+
381
+ // 4. Retry a failed delivery attempt
382
+ if (deliveries.items.length > 0) {
383
+ await client.webhooks.retryDelivery(deliveries.items[0].id);
384
+ }
385
+
386
+ // 5. Verify HMAC-SHA256 signature in your receiving server/microservice
387
+ const isValid = client.webhooks.verifySignature(
388
+ rawRequestBodyString,
389
+ requestHeaders['x-nodestack-signature'],
390
+ 'whsec_9948a8cf38b1e42c'
391
+ );
392
+ ```
393
+
394
+ ---
395
+
283
396
  ## 📄 License
284
397
 
285
398
  MIT ÂĐ NodeStack Contributors
package/dist/Client.d.ts CHANGED
@@ -5,6 +5,9 @@ import { RealtimeService } from './services/RealtimeService';
5
5
  import { AdminService } from './services/AdminService';
6
6
  import { CollectionService } from './services/CollectionService';
7
7
  import { DemoService } from './services/DemoService';
8
+ import { SnapshotService } from './services/SnapshotService';
9
+ import { WebhookService } from './services/WebhookService';
10
+ import { AppService } from './services/AppService';
8
11
  import type { ClientOptions, SendOptions, RecordModel, ChaosOptions } from './types';
9
12
  /**
10
13
  * Main NodeStack Client SDK entry point.
@@ -16,7 +19,11 @@ export declare class NodeStackClient<TCollections extends Record<string, any> =
16
19
  readonly realtime: RealtimeService;
17
20
  readonly admins: AdminService;
18
21
  readonly collections: CollectionService;
22
+ readonly snapshots: SnapshotService;
19
23
  readonly demo: DemoService;
24
+ readonly webhooks: WebhookService;
25
+ readonly apps: AppService;
26
+ readonly appSlug?: string;
20
27
  private customFetch?;
21
28
  private defaultHeaders;
22
29
  private defaultTimeout;
@@ -63,5 +70,10 @@ export declare class NodeStackClient<TCollections extends Record<string, any> =
63
70
  * Fetches system metrics & analytics (Admin only).
64
71
  */
65
72
  metrics(): Promise<any>;
73
+ /**
74
+ * Returns a new NodeStackClient instance scoped to a specific application/MVP.
75
+ * Inherits baseUrl, authStore, customFetch, defaultHeaders, and defaultTimeout.
76
+ */
77
+ app<TAppCollections extends Record<string, any> = Record<string, RecordModel>>(slug: string, options?: Partial<ClientOptions>): NodeStackClient<TAppCollections>;
66
78
  }
67
79
  //# sourceMappingURL=Client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Client.d.ts","sourceRoot":"","sources":["../src/Client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,qBAAa,eAAe,CAAC,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IACjG,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,SAAgB,SAAS,EAAE,aAAa,CAAC;IACzC,SAAgB,KAAK,EAAE,WAAW,CAAC;IACnC,SAAgB,QAAQ,EAAE,eAAe,CAAC;IAC1C,SAAgB,MAAM,EAAE,YAAY,CAAC;IACrC,SAAgB,WAAW,EAAE,iBAAiB,CAAC;IAC/C,SAAgB,IAAI,EAAE,WAAW,CAAC;IAElC,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAA8C;gBAExD,OAAO,GAAE,MAAY,EAAE,OAAO,GAAE,aAAkB;IAe9D;;;OAGG;IACI,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IAInD;;OAEG;IACI,QAAQ,IAAI,YAAY,GAAG,IAAI;IAItC;;;;OAIG;IACI,UAAU,CAAC,CAAC,SAAS,MAAM,YAAY,EAAE,IAAI,EAAE,CAAC,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACjF,UAAU,CAAC,CAAC,GAAG,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC;IAWlE;;;OAGG;IACU,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;IAsJ/E;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAItE;;OAEG;IACU,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC;IAIrC;;OAEG;IACU,IAAI,CAAC,IAAI,SAAI,EAAE,OAAO,SAAK,GAAG,OAAO,CAAC,GAAG,CAAC;IAOvD;;OAEG;IACU,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC;CAGrC"}
1
+ {"version":3,"file":"Client.d.ts","sourceRoot":"","sources":["../src/Client.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BaseAuthStore.d.ts","sourceRoot":"","sources":["../../src/auth/BaseAuthStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;AAEjG;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAqC1E;AAED;;GAEG;AACH,8BAAsB,aAAa;IACjC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAM;IAC9B,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAAQ;IACzD,OAAO,CAAC,UAAU,CAAsC;IAExD,IAAW,KAAK,IAAI,MAAM,CAEzB;IAED,IAAW,KAAK,IAAI,WAAW,GAAG,UAAU,GAAG,IAAI,CAElD;IAED;;OAEG;IACH,IAAW,OAAO,IAAI,OAAO,CAgB5B;IAED;;OAEG;IACI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,IAAI;IAMzE;;OAEG;IACI,KAAK,IAAI,IAAI;IAMpB;;;OAGG;IACI,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,eAAe,UAAQ,GAAG,MAAM,IAAI;IAelF;;OAEG;IACH,SAAS,CAAC,MAAM,IAAI,IAAI;CASzB"}
1
+ {"version":3,"file":"BaseAuthStore.d.ts","sourceRoot":"","sources":["../../src/auth/BaseAuthStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;AAEjG;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAqC1E;AAED;;GAEG;AACH,8BAAsB,aAAa;IACjC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAM;IAC9B,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAAQ;IACzD,OAAO,CAAC,UAAU,CAAsC;IAExD,IAAW,KAAK,IAAI,MAAM,CAEzB;IAED,IAAW,KAAK,IAAI,WAAW,GAAG,UAAU,GAAG,IAAI,CAElD;IAED;;OAEG;IACH,IAAW,OAAO,IAAI,OAAO,CAgB5B;IAED;;OAEG;IACI,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,IAAI,CAIxE;IAED;;OAEG;IACI,KAAK,IAAI,IAAI,CAInB;IAED;;;OAGG;IACI,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,eAAe,UAAQ,GAAG,MAAM,IAAI,CAajF;IAED;;OAEG;IACH,SAAS,CAAC,MAAM,IAAI,IAAI,CAQvB;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"LocalAuthStore.d.ts","sourceRoot":"","sources":["../../src/auth/LocalAuthStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAID;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAC,CAAc;gBAElB,UAAU,GAAE,MAA4B,EAAE,aAAa,CAAC,EAAE,WAAW;IAajF,OAAO,CAAC,WAAW;IAiBH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,IAAI;IAkBlE,KAAK,IAAI,IAAI;CAW9B"}
1
+ {"version":3,"file":"LocalAuthStore.d.ts","sourceRoot":"","sources":["../../src/auth/LocalAuthStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAID;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAC,CAAc;IAE9B,YAAY,UAAU,GAAE,MAA4B,EAAE,aAAa,CAAC,EAAE,WAAW,EAWhF;IAED,OAAO,CAAC,WAAW;IAiBH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,IAAI,CAgBjF;IAEe,KAAK,IAAI,IAAI,CAU5B;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"ClientResponseError.d.ts","sourceRoot":"","sources":["../../src/errors/ClientResponseError.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,EAAE,GAAG,CAAC;IAC1B,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,SAAgB,aAAa,CAAC,EAAE,GAAG,CAAC;gBAExB,OAAO,GAAE,0BAA+B;IAmB7C,MAAM;;;;;;;;CAUd"}
1
+ {"version":3,"file":"ClientResponseError.d.ts","sourceRoot":"","sources":["../../src/errors/ClientResponseError.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,EAAE,GAAG,CAAC;IAC1B,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,SAAgB,aAAa,CAAC,EAAE,GAAG,CAAC;IAEpC,YAAY,OAAO,GAAE,0BAA+B,EAiBnD;IAEM,MAAM;QAET,IAAI;QACJ,OAAO;QACP,GAAG;QACH,MAAM;QACN,IAAI;QACJ,OAAO;MAEV;CACF"}
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AdminService: () => AdminService,
24
+ AppService: () => AppService,
24
25
  BaseAuthStore: () => BaseAuthStore,
25
26
  BaseService: () => BaseService,
26
27
  ClientResponseError: () => ClientResponseError,
@@ -32,8 +33,12 @@ __export(index_exports, {
32
33
  NodeStackClient: () => NodeStackClient,
33
34
  RealtimeService: () => RealtimeService,
34
35
  RecordService: () => RecordService,
36
+ SnapshotService: () => SnapshotService,
37
+ WebhookService: () => WebhookService,
38
+ computeWebhookSignature: () => computeWebhookSignature,
35
39
  decodeJwtPayload: () => decodeJwtPayload,
36
- default: () => NodeStackClient
40
+ default: () => NodeStackClient,
41
+ verifyWebhookSignature: () => verifyWebhookSignature
37
42
  });
38
43
  module.exports = __toCommonJS(index_exports);
39
44
 
@@ -463,7 +468,8 @@ var FileService = class extends BaseService {
463
468
  const cleanCol = encodeURIComponent(collectionName);
464
469
  const cleanId = encodeURIComponent(recordId);
465
470
  const cleanFile = encodeURIComponent(filename);
466
- let url = `${cleanBase}/api/files/${cleanCol}/${cleanId}/${cleanFile}`;
471
+ const appSlug = options.app || this.client.appSlug;
472
+ let url = appSlug && appSlug !== "default" ? `${cleanBase}/api/apps/${encodeURIComponent(appSlug)}/files/${cleanCol}/${cleanId}/${cleanFile}` : `${cleanBase}/api/files/${cleanCol}/${cleanId}/${cleanFile}`;
467
473
  const queryParts = [];
468
474
  if (options.download) {
469
475
  queryParts.push("download=1");
@@ -536,8 +542,16 @@ var RealtimeService = class extends BaseService {
536
542
  const EventSourceClass = this.getEventSourceConstructor();
537
543
  const base = (this.client.baseUrl || "").replace(/\/+$/, "");
538
544
  let url = `${base}/api/realtime`;
545
+ const queryParams = new URLSearchParams();
539
546
  if (this.client.authStore.token) {
540
- url += `?token=${encodeURIComponent(this.client.authStore.token)}`;
547
+ queryParams.append("token", this.client.authStore.token);
548
+ }
549
+ if (this.client.appSlug) {
550
+ queryParams.append("app", this.client.appSlug);
551
+ }
552
+ const qs = queryParams.toString();
553
+ if (qs) {
554
+ url += `?${qs}`;
541
555
  }
542
556
  this.eventSource = new EventSourceClass(url);
543
557
  const handleConnect = async (event) => {
@@ -769,6 +783,8 @@ var AdminService = class extends BaseService {
769
783
 
770
784
  // src/services/CollectionService.ts
771
785
  var CollectionService = class extends BaseService {
786
+ list = this.getList.bind(this);
787
+ get = this.getOne.bind(this);
772
788
  /**
773
789
  * Retrieves all collection schema models.
774
790
  */
@@ -875,15 +891,288 @@ var DemoService = class extends BaseService {
875
891
  }
876
892
  };
877
893
 
894
+ // src/services/SnapshotService.ts
895
+ var SnapshotService = class extends BaseService {
896
+ /**
897
+ * Retrieves all snapshots, active baseline, and live drift information.
898
+ */
899
+ async list() {
900
+ return this.send("/api/snapshots", {
901
+ method: "GET"
902
+ });
903
+ }
904
+ /**
905
+ * Alias for list() to retrieve status.
906
+ */
907
+ async getStatus() {
908
+ return this.list();
909
+ }
910
+ /**
911
+ * Creates a new application state snapshot.
912
+ * If snapshot limit (10) is reached, evictOldest: true can be passed to confirm eviction of the oldest.
913
+ */
914
+ async create(options) {
915
+ return this.send("/api/snapshots", {
916
+ method: "POST",
917
+ body: options || {}
918
+ });
919
+ }
920
+ /**
921
+ * Retrieves a specific snapshot by ID.
922
+ */
923
+ async get(id) {
924
+ return this.send(`/api/snapshots/${encodeURIComponent(id)}`, {
925
+ method: "GET"
926
+ });
927
+ }
928
+ /**
929
+ * Restores application state to a specific snapshot.
930
+ * Note: Future snapshots are NOT deleted; history remains intact.
931
+ */
932
+ async restore(id) {
933
+ return this.send(`/api/snapshots/${encodeURIComponent(id)}/restore`, {
934
+ method: "POST"
935
+ });
936
+ }
937
+ /**
938
+ * Deletes a specific snapshot by ID.
939
+ */
940
+ async delete(id) {
941
+ return this.send(`/api/snapshots/${encodeURIComponent(id)}`, {
942
+ method: "DELETE"
943
+ });
944
+ }
945
+ /**
946
+ * Clears all saved snapshots.
947
+ */
948
+ async clear() {
949
+ return this.send("/api/snapshots", {
950
+ method: "DELETE"
951
+ });
952
+ }
953
+ };
954
+
955
+ // src/services/WebhookService.ts
956
+ function computeWebhookSignature(payload, secret) {
957
+ const str = typeof payload === "string" ? payload : JSON.stringify(payload);
958
+ try {
959
+ const nodeCrypto = typeof require !== "undefined" ? require("crypto") : typeof globalThis !== "undefined" ? globalThis.require?.("crypto") || globalThis.crypto : null;
960
+ if (nodeCrypto && typeof nodeCrypto.createHmac === "function") {
961
+ return nodeCrypto.createHmac("sha256", secret).update(str, "utf8").digest("hex");
962
+ }
963
+ } catch {
964
+ }
965
+ throw new Error("[NodeStackClient] HMAC-SHA256 computation requires crypto module.");
966
+ }
967
+ function verifyWebhookSignature(payload, signatureHeader, secret) {
968
+ if (!signatureHeader || !secret) return false;
969
+ let expected = "";
970
+ try {
971
+ expected = computeWebhookSignature(payload, secret);
972
+ } catch {
973
+ return false;
974
+ }
975
+ let cleanHeader = signatureHeader.trim();
976
+ if (cleanHeader.startsWith("sha256=")) {
977
+ cleanHeader = cleanHeader.substring(7);
978
+ } else if (cleanHeader.startsWith("sha256:")) {
979
+ cleanHeader = cleanHeader.substring(7);
980
+ }
981
+ if (cleanHeader.length !== expected.length) return false;
982
+ try {
983
+ const nodeCrypto = typeof require !== "undefined" ? require("crypto") : typeof globalThis !== "undefined" ? globalThis.require?.("crypto") : null;
984
+ const buf = typeof Buffer !== "undefined" ? Buffer : typeof globalThis !== "undefined" ? globalThis.Buffer : null;
985
+ if (nodeCrypto && typeof nodeCrypto.timingSafeEqual === "function" && buf) {
986
+ return nodeCrypto.timingSafeEqual(buf.from(cleanHeader, "hex"), buf.from(expected, "hex"));
987
+ }
988
+ } catch {
989
+ }
990
+ let diff = 0;
991
+ for (let i = 0; i < cleanHeader.length; i++) {
992
+ diff |= cleanHeader.charCodeAt(i) ^ expected.charCodeAt(i);
993
+ }
994
+ return diff === 0;
995
+ }
996
+ var WebhookService = class extends BaseService {
997
+ /**
998
+ * Retrieves all configured outbound webhooks.
999
+ */
1000
+ async getList(options = {}) {
1001
+ const res = await this.send("/api/webhooks", {
1002
+ method: "GET",
1003
+ query: options
1004
+ });
1005
+ return Array.isArray(res) ? res : [];
1006
+ }
1007
+ /**
1008
+ * Retrieves a single webhook by ID.
1009
+ */
1010
+ async getOne(id) {
1011
+ return this.send(`/api/webhooks/${encodeURIComponent(id)}`, {
1012
+ method: "GET"
1013
+ });
1014
+ }
1015
+ /**
1016
+ * Creates a new outbound webhook configuration.
1017
+ */
1018
+ async create(data) {
1019
+ return this.send("/api/webhooks", {
1020
+ method: "POST",
1021
+ body: data
1022
+ });
1023
+ }
1024
+ /**
1025
+ * Updates an existing webhook configuration.
1026
+ */
1027
+ async update(id, data) {
1028
+ return this.send(`/api/webhooks/${encodeURIComponent(id)}`, {
1029
+ method: "PATCH",
1030
+ body: data
1031
+ });
1032
+ }
1033
+ /**
1034
+ * Deletes a configured webhook by ID.
1035
+ */
1036
+ async delete(id) {
1037
+ await this.send(`/api/webhooks/${encodeURIComponent(id)}`, {
1038
+ method: "DELETE"
1039
+ });
1040
+ return true;
1041
+ }
1042
+ /**
1043
+ * Dispatches a live test event to the target webhook endpoint with an optional payload.
1044
+ */
1045
+ async test(id, payload) {
1046
+ return this.send(`/api/webhooks/${encodeURIComponent(id)}/test`, {
1047
+ method: "POST",
1048
+ body: payload !== void 0 ? { payload } : {}
1049
+ });
1050
+ }
1051
+ /**
1052
+ * Retrieves overall webhook health and delivery statistics.
1053
+ */
1054
+ async getStats() {
1055
+ return this.send("/api/webhooks/stats", {
1056
+ method: "GET"
1057
+ });
1058
+ }
1059
+ /**
1060
+ * Queries paginated webhook delivery logs with optional filtering by webhook ID, collection, status, or search.
1061
+ */
1062
+ async getDeliveries(options = {}) {
1063
+ return this.send("/api/webhooks/deliveries", {
1064
+ method: "GET",
1065
+ query: options
1066
+ });
1067
+ }
1068
+ /**
1069
+ * Retrieves complete delivery log details including headers and request/response payloads.
1070
+ */
1071
+ async getDelivery(id) {
1072
+ return this.send(`/api/webhooks/deliveries/${encodeURIComponent(id)}`, {
1073
+ method: "GET"
1074
+ });
1075
+ }
1076
+ /**
1077
+ * Manually retries a failed delivery attempt to its target endpoint.
1078
+ */
1079
+ async retryDelivery(id) {
1080
+ return this.send(`/api/webhooks/deliveries/${encodeURIComponent(id)}/retry`, {
1081
+ method: "POST"
1082
+ });
1083
+ }
1084
+ /**
1085
+ * Clears delivery history logs. If olderThanDays is specified, only deletes logs older than that many days.
1086
+ */
1087
+ async clearDeliveries(olderThanDays) {
1088
+ const query = {};
1089
+ if (olderThanDays !== void 0 && olderThanDays !== null) {
1090
+ query.olderThanDays = olderThanDays;
1091
+ }
1092
+ return this.send("/api/webhooks/deliveries", {
1093
+ method: "DELETE",
1094
+ query
1095
+ });
1096
+ }
1097
+ /**
1098
+ * Verifies an incoming X-NodeStack-Signature header against the expected secret.
1099
+ */
1100
+ verifySignature(payload, signatureHeader, secret) {
1101
+ return verifyWebhookSignature(payload, signatureHeader, secret);
1102
+ }
1103
+ /**
1104
+ * Computes an HMAC-SHA256 signature for a payload and secret string.
1105
+ */
1106
+ computeSignature(payload, secret) {
1107
+ return computeWebhookSignature(payload, secret);
1108
+ }
1109
+ static verifySignature = verifyWebhookSignature;
1110
+ static computeSignature = computeWebhookSignature;
1111
+ };
1112
+
1113
+ // src/services/AppService.ts
1114
+ var AppService = class extends BaseService {
1115
+ list = this.getList.bind(this);
1116
+ get = this.getOne.bind(this);
1117
+ /**
1118
+ * Retrieves all configured applications.
1119
+ */
1120
+ async getList() {
1121
+ const res = await this.send("/api/apps", {
1122
+ method: "GET"
1123
+ });
1124
+ return Array.isArray(res) ? res : res?.items || [];
1125
+ }
1126
+ /**
1127
+ * Retrieves a single application by ID or slug.
1128
+ */
1129
+ async getOne(appId) {
1130
+ return this.send(`/api/apps/${encodeURIComponent(appId)}`, {
1131
+ method: "GET"
1132
+ });
1133
+ }
1134
+ /**
1135
+ * Creates a new application.
1136
+ */
1137
+ async create(data) {
1138
+ return this.send("/api/apps", {
1139
+ method: "POST",
1140
+ body: data
1141
+ });
1142
+ }
1143
+ /**
1144
+ * Updates an existing application.
1145
+ */
1146
+ async update(appId, data) {
1147
+ return this.send(`/api/apps/${encodeURIComponent(appId)}`, {
1148
+ method: "PATCH",
1149
+ body: data
1150
+ });
1151
+ }
1152
+ /**
1153
+ * Deletes an application by ID or slug.
1154
+ */
1155
+ async delete(appId) {
1156
+ await this.send(`/api/apps/${encodeURIComponent(appId)}`, {
1157
+ method: "DELETE"
1158
+ });
1159
+ return true;
1160
+ }
1161
+ };
1162
+
878
1163
  // src/Client.ts
879
- var NodeStackClient = class {
1164
+ var NodeStackClient = class _NodeStackClient {
880
1165
  baseUrl;
881
1166
  authStore;
882
1167
  files;
883
1168
  realtime;
884
1169
  admins;
885
1170
  collections;
1171
+ snapshots;
886
1172
  demo;
1173
+ webhooks;
1174
+ apps;
1175
+ appSlug;
887
1176
  customFetch;
888
1177
  defaultHeaders;
889
1178
  defaultTimeout;
@@ -896,11 +1185,15 @@ var NodeStackClient = class {
896
1185
  this.defaultHeaders = options.headers || {};
897
1186
  this.defaultTimeout = options.timeout || 12e4;
898
1187
  this.chaosOptions = options.chaos ? { ...options.chaos } : null;
1188
+ this.appSlug = options.app;
899
1189
  this.files = new FileService(this);
900
1190
  this.realtime = new RealtimeService(this, options.EventSource);
901
1191
  this.admins = new AdminService(this);
902
1192
  this.collections = new CollectionService(this);
1193
+ this.snapshots = new SnapshotService(this);
903
1194
  this.demo = new DemoService(this);
1195
+ this.webhooks = new WebhookService(this);
1196
+ this.apps = new AppService(this);
904
1197
  }
905
1198
  /**
906
1199
  * Dynamically configure latency & chaos simulation options for all future requests.
@@ -954,6 +1247,9 @@ var NodeStackClient = class {
954
1247
  if (this.authStore.token && !headers["Authorization"] && !headers["authorization"]) {
955
1248
  headers["Authorization"] = `Bearer ${this.authStore.token}`;
956
1249
  }
1250
+ if (this.appSlug && !headers["x-nodestack-app"] && !headers["X-NodeStack-App"]) {
1251
+ headers["x-nodestack-app"] = this.appSlug;
1252
+ }
957
1253
  const effectiveChaos = {
958
1254
  ...this.chaosOptions || {},
959
1255
  ...options.chaos || {},
@@ -1070,6 +1366,21 @@ var NodeStackClient = class {
1070
1366
  async metrics() {
1071
1367
  return this.send("/api/metrics", { method: "GET" });
1072
1368
  }
1369
+ /**
1370
+ * Returns a new NodeStackClient instance scoped to a specific application/MVP.
1371
+ * Inherits baseUrl, authStore, customFetch, defaultHeaders, and defaultTimeout.
1372
+ */
1373
+ app(slug, options = {}) {
1374
+ return new _NodeStackClient(this.baseUrl, {
1375
+ authStore: options.authStore || this.authStore,
1376
+ fetch: options.fetch || this.customFetch,
1377
+ headers: { ...this.defaultHeaders, ...options.headers || {} },
1378
+ timeout: options.timeout !== void 0 ? options.timeout : this.defaultTimeout,
1379
+ chaos: options.chaos !== void 0 ? options.chaos ? { ...options.chaos } : void 0 : this.chaosOptions ? { ...this.chaosOptions } : void 0,
1380
+ app: slug,
1381
+ EventSource: options.EventSource || this.realtime.customEventSource
1382
+ });
1383
+ }
1073
1384
  };
1074
1385
 
1075
1386
  // src/auth/MemoryAuthStore.ts