@seaverse/dataservice 1.1.0 → 1.3.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,6 +1,15 @@
1
- # SeaVerse Data Service SDK
1
+ # @seaverse/dataservice
2
2
 
3
- TypeScript/JavaScript SDK for universal data storage with PostgREST.
3
+ A TypeScript/JavaScript SDK for universal data storage with PostgREST backend. Store and query JSON data with automatic user isolation and type safety.
4
+
5
+ ## Features
6
+
7
+ - **Type-safe**: Full TypeScript support with generic types
8
+ - **Secure**: Built on PostgreSQL Row-Level Security (RLS)
9
+ - **Flexible**: Store any JSON data structure
10
+ - **Query builder**: Fluent API for complex queries
11
+ - **Auto-extraction**: Automatically extracts app ID from URL
12
+ - **UUID support**: Client-side or server-side ID generation
4
13
 
5
14
  ## Installation
6
15
 
@@ -13,169 +22,271 @@ npm install @seaverse/dataservice
13
22
  ```typescript
14
23
  import { createClient } from '@seaverse/dataservice';
15
24
 
16
- // appId is automatically extracted from current URL
25
+ // Create client (Bearer prefix auto-added, uses default SeaVerse API, appId auto-extracted from URL)
17
26
  const client = createClient({
18
- url: 'https://your-postgrest-api.example.com',
19
- token: 'Bearer your-jwt-token-here',
27
+ token: 'your-jwt-token',
20
28
  });
21
29
 
22
- // Insert
23
- const order = await client.userData
24
- .collection('orders')
25
- .insert({
26
- order_number: 'ORD-001',
27
- status: 'pending',
28
- total: 99.99,
29
- });
30
+ // Access user data table
31
+ const orders = client.userData.collection('orders');
30
32
 
31
- // Get by ID
32
- const record = await client.userData
33
- .collection('orders')
34
- .get(order.id);
33
+ // Insert data
34
+ const order = await orders.insert({
35
+ order_number: 'ORD-001',
36
+ status: 'pending',
37
+ total: 99.99,
38
+ });
35
39
 
36
- // Query
37
- const results = await client.userData
38
- .collection('orders')
40
+ // Query data
41
+ const pending = await orders
39
42
  .select()
40
43
  .eq('data->>status', 'pending')
41
44
  .order('created_at', { descending: true })
42
- .limit(10)
43
45
  .execute();
44
46
 
45
- // Update
46
- await client.userData
47
- .collection('orders')
48
- .patch(order.id, { status: 'completed' });
47
+ // Update data
48
+ await orders.patch(order.id, { status: 'completed' });
49
+
50
+ // Delete single record
51
+ await orders.delete(order.id);
49
52
 
50
- // Delete
51
- await client.userData
52
- .collection('orders')
53
- .delete(order.id);
53
+ // Or delete all records in collection
54
+ const deletedCount = await orders.deleteAll();
54
55
  ```
55
56
 
56
57
  ## Core Concepts
57
58
 
58
- ### Hierarchy
59
+ ### Data Hierarchy
59
60
 
60
61
  ```
61
- Client → DataTable (user_data) → Collection (collection_name) → Data (JSONB)
62
+ DataServiceClient
63
+ └── DataTable (e.g., userData)
64
+ └── Collection (e.g., "orders")
65
+ └── DataRecord (JSONB data + metadata)
62
66
  ```
63
67
 
64
- ### Application ID (appId)
68
+ ### Application ID
65
69
 
66
- **Automatic Extraction**: The SDK automatically extracts `app_id` from the current URL:
70
+ The SDK automatically extracts `app_id` from the current environment:
67
71
 
72
+ **Browser**: Extracted from URL hostname
68
73
  ```
69
- URL: https://app_8e5e867e-user_f4ed2364ffcf24d6c6707d5ca5e4fe6d.app.seaverse.ai
70
- → appId: app_8e5e867e-user_f4ed2364ffcf24d6c6707d5ca5e4fe6d
71
-
72
- URL: https://example.com
73
- → appId: example.com
74
+ https://app_8e5e867e-user_f4ed2364.app.seaverse.ai
75
+ → appId: "app_8e5e867e-user_f4ed2364"
74
76
  ```
75
77
 
76
- **Node.js Environment**: Set via environment variable:
78
+ **Node.js**: Set via environment variable
77
79
  ```bash
78
80
  export SEAVERSE_APP_ID=my-app-id
79
81
  ```
80
82
 
81
- ### Unique Constraint
83
+ Access the extracted ID:
84
+ ```typescript
85
+ console.log(client.appId); // "app_8e5e867e-user_f4ed2364"
86
+ ```
87
+
88
+ ### Data Tables
89
+
90
+ The SDK provides access to different data tables with different permission scopes:
91
+
92
+ - **`userData`**: User-specific data (isolated by user_id)
93
+ - More tables coming soon (public data, shared data, etc.)
82
94
 
83
- **Important**: Only ONE record per `(user_id, app_id, collection_name)`.
95
+ ### Collections
84
96
 
85
- For multiple records, use unique collection names:
97
+ Collections are logical groupings within a table. Each collection stores one record per `(user_id, app_id, collection_name)` combination.
98
+
99
+ **Important**: To store multiple records, use unique collection names:
86
100
 
87
101
  ```typescript
88
- // ✓ Correct
89
- await client.userData.collection('orders_1').insert(order1);
90
- await client.userData.collection('orders_2').insert(order2);
102
+ // ✓ Correct: Unique collection names
103
+ await client.userData.collection('order_1').insert(order1);
104
+ await client.userData.collection('order_2').insert(order2);
105
+
106
+ // ✓ Or use batch insert helper
107
+ await client.userData.batchInsert('order', [order1, order2]);
108
+ // Creates: order_0, order_1, order_2, ...
109
+
110
+ // ✗ Wrong: Same collection name
111
+ await client.userData.collection('order').insert(order1);
112
+ await client.userData.collection('order').insert(order2); // Error: 409 Conflict
113
+ ```
91
114
 
92
- // Or use batch insert
93
- await client.userData.batchInsert('orders', [order1, order2]);
115
+ ### Data Records
94
116
 
95
- // Wrong - will fail
96
- await client.userData.collection('orders').insert(order1);
97
- await client.userData.collection('orders').insert(order2); // Error: 409 Conflict
117
+ Each record contains:
118
+
119
+ ```typescript
120
+ interface DataRecord<T> {
121
+ id: string; // UUID primary key
122
+ user_id: string; // Owner user ID
123
+ app_id: string; // Application ID
124
+ collection_name: string; // Collection identifier
125
+ data: T; // Your JSON data
126
+ created_at: string; // ISO timestamp
127
+ updated_at: string; // ISO timestamp
128
+ deleted_at: string | null; // Soft delete timestamp
129
+ }
98
130
  ```
99
131
 
100
132
  ## API Reference
101
133
 
102
- ### Client
134
+ ### Client Creation
103
135
 
104
136
  ```typescript
105
- createClient(config: ClientConfig): DataServiceClient
137
+ import { createClient } from '@seaverse/dataservice';
106
138
 
107
- interface ClientConfig {
108
- url: string; // PostgREST API URL
109
- token: string; // JWT token with user_id in payload
139
+ const client = createClient({
140
+ token: string; // JWT token (Bearer prefix optional, auto-added if missing)
141
+ url?: string; // PostgREST API URL (default: https://dataservice-api.seaverse.ai)
110
142
  options?: {
111
- timeout?: number; // Request timeout (ms, default: 30000)
112
- headers?: Record<string, string>;
143
+ timeout?: number; // Request timeout in ms (default: 30000)
144
+ headers?: Record<string, string>; // Additional headers
113
145
  };
114
- }
146
+ });
147
+ ```
115
148
 
116
- // Client properties
117
- client.appId: string // Auto-extracted from URL
118
- client.userData: DataTable
149
+ **Examples:**
150
+
151
+ ```typescript
152
+ // Use default SeaVerse API (Bearer prefix auto-added)
153
+ const client = createClient({
154
+ token: 'your-jwt-token',
155
+ });
156
+
157
+ // Or with explicit Bearer prefix (both work)
158
+ const client = createClient({
159
+ token: 'Bearer your-jwt-token',
160
+ });
161
+
162
+ // Use custom API endpoint
163
+ const client = createClient({
164
+ url: 'https://your-custom-api.example.com',
165
+ token: 'your-jwt-token',
166
+ });
119
167
  ```
120
168
 
121
- ### DataTable
169
+ ### DataTable Methods
122
170
 
123
171
  ```typescript
172
+ // Get a collection
124
173
  client.userData.collection<T>(name: string): Collection<T>
125
- client.userData.batchInsert<T>(baseName: string, records: T[]): Promise<DataRecord<T>[]>
174
+
175
+ // Batch insert multiple records
176
+ client.userData.batchInsert<T>(
177
+ baseName: string,
178
+ records: T[]
179
+ ): Promise<DataRecord<T>[]>
126
180
  ```
127
181
 
128
- ### Collection
182
+ ### Collection Methods
183
+
184
+ #### Create
129
185
 
130
186
  ```typescript
131
- // Create
132
- collection.insert(data: T, id?: string): Promise<DataRecord<T>> // Optional UUID
187
+ // Insert with auto-generated UUID
188
+ collection.insert(data: T): Promise<DataRecord<T>>
133
189
 
134
- // Read
190
+ // Insert with custom UUID
191
+ collection.insert(data: T, id: string): Promise<DataRecord<T>>
192
+ ```
193
+
194
+ #### Read
195
+
196
+ ```typescript
197
+ // Get by ID
135
198
  collection.get(id: string): Promise<DataRecord<T> | null>
136
199
  collection.selectById(id: string): Promise<DataRecord<T> | null>
200
+
201
+ // Query builder
137
202
  collection.select(): QueryBuilder<T>
203
+
204
+ // Search by criteria
138
205
  collection.search(criteria: Partial<T>): Promise<DataRecord<T>[]>
206
+
207
+ // Count records
139
208
  collection.count(): Promise<number>
209
+ ```
210
+
211
+ #### Update
140
212
 
141
- // Update
213
+ ```typescript
214
+ // Full update (replaces entire data object)
142
215
  collection.update(id: string, data: T): Promise<DataRecord<T>>
216
+
217
+ // Partial update (merges with existing data)
143
218
  collection.patch(id: string, partial: Partial<T>): Promise<DataRecord<T>>
219
+ ```
220
+
221
+ #### Delete
144
222
 
145
- // Delete
223
+ ```typescript
224
+ // Hard delete single record (permanent)
146
225
  collection.delete(id: string): Promise<void>
226
+
227
+ // Hard delete all records in collection (returns count of deleted records)
228
+ collection.deleteAll(): Promise<number>
229
+
230
+ // Soft delete (sets deleted_at timestamp)
147
231
  collection.softDelete(id: string): Promise<boolean>
232
+
233
+ // Restore soft-deleted record
148
234
  collection.restore(id: string): Promise<boolean>
149
235
  ```
150
236
 
151
- **Note**: All IDs are UUIDs (strings). Database auto-generates if not provided.
152
-
153
237
  ### Query Builder
154
238
 
239
+ Build complex queries with a fluent API:
240
+
155
241
  ```typescript
156
- query
157
- .eq(field, value) // Equal
158
- .neq(field, value) // Not equal
159
- .gt(field, value) // Greater than
160
- .gte(field, value) // Greater than or equal
161
- .lt(field, value) // Less than
162
- .lte(field, value) // Less than or equal
163
- .like(field, pattern) // Pattern match (case-sensitive)
164
- .ilike(field, pattern) // Pattern match (case-insensitive)
165
- .in(field, values) // Value in array
166
- .contains(value) // JSONB contains
167
- .order(field, options) // Sort
168
- .limit(count) // Limit results
169
- .offset(count) // Skip results
170
- .execute() // Get results
171
- .count() // Get count
242
+ collection.select()
243
+ // Comparison operators
244
+ .eq(field: string, value: any) // Equal
245
+ .neq(field: string, value: any) // Not equal
246
+ .gt(field: string, value: any) // Greater than
247
+ .gte(field: string, value: any) // Greater than or equal
248
+ .lt(field: string, value: any) // Less than
249
+ .lte(field: string, value: any) // Less than or equal
250
+
251
+ // Pattern matching
252
+ .like(field: string, pattern: string) // Case-sensitive
253
+ .ilike(field: string, pattern: string) // Case-insensitive
254
+
255
+ // Array operations
256
+ .in(field: string, values: any[]) // Value in array
257
+
258
+ // JSONB operations
259
+ .contains(value: any) // JSONB contains
260
+
261
+ // Sorting and pagination
262
+ .order(field: string, options?: { descending?: boolean })
263
+ .limit(count: number)
264
+ .offset(count: number)
265
+
266
+ // Execute
267
+ .execute(): Promise<DataRecord<T>[]>
268
+ .count(): Promise<number>
172
269
  ```
173
270
 
174
- ### Client Methods
271
+ **Field syntax for JSONB queries**:
272
+ - Use `data->>field` for text comparison: `.eq('data->>status', 'pending')`
273
+ - Use `data->field` for numeric comparison: `.gt('data->total', '100')`
274
+
275
+ ### Client Utility Methods
175
276
 
176
277
  ```typescript
177
- client.getStats(): Promise<UserDataStats>
178
- client.health(): Promise<{ status: string; user_id: string }>
278
+ // Get user data statistics
279
+ client.getStats(): Promise<{
280
+ total_records: number;
281
+ total_collections: number;
282
+ storage_bytes: number;
283
+ }>
284
+
285
+ // Health check
286
+ client.health(): Promise<{
287
+ status: string;
288
+ user_id: string;
289
+ }>
179
290
  ```
180
291
 
181
292
  ## Examples
@@ -183,102 +294,181 @@ client.health(): Promise<{ status: string; user_id: string }>
183
294
  ### E-Commerce Orders
184
295
 
185
296
  ```typescript
297
+ import { createClient } from '@seaverse/dataservice';
298
+
186
299
  type Order = {
187
300
  order_number: string;
188
301
  customer_email: string;
189
- items: Array<{ product_id: string; quantity: number; price: number }>;
302
+ items: Array<{
303
+ product_id: string;
304
+ quantity: number;
305
+ price: number;
306
+ }>;
190
307
  status: 'pending' | 'processing' | 'shipped' | 'delivered';
191
308
  total: number;
309
+ notes?: string;
192
310
  };
193
311
 
312
+ const client = createClient({
313
+ token: process.env.JWT_TOKEN!,
314
+ });
315
+
194
316
  const orders = client.userData.collection<Order>('orders');
195
317
 
196
- // Create
318
+ // Create order
197
319
  const order = await orders.insert({
198
320
  order_number: `ORD-${Date.now()}`,
199
321
  customer_email: 'customer@example.com',
200
- items: [{ product_id: 'PROD-001', quantity: 2, price: 29.99 }],
322
+ items: [
323
+ { product_id: 'PROD-001', quantity: 2, price: 29.99 },
324
+ { product_id: 'PROD-002', quantity: 1, price: 49.99 },
325
+ ],
201
326
  status: 'pending',
202
- total: 59.98,
327
+ total: 109.97,
203
328
  });
204
329
 
205
- // Query
206
- const pending = await orders
330
+ console.log('Order created:', order.id);
331
+
332
+ // Query pending orders over $50
333
+ const pendingOrders = await orders
207
334
  .select()
208
335
  .eq('data->>status', 'pending')
209
- .gt('data->>total', '50')
336
+ .gt('data->total', '50')
210
337
  .order('created_at', { descending: true })
338
+ .limit(10)
211
339
  .execute();
212
340
 
213
- // Update
341
+ console.log(`Found ${pendingOrders.length} pending orders`);
342
+
343
+ // Update order status
214
344
  await orders.patch(order.id, { status: 'shipped' });
345
+
346
+ // Search by customer email
347
+ const customerOrders = await orders.search({
348
+ customer_email: 'customer@example.com',
349
+ });
350
+
351
+ // Delete a specific order
352
+ await orders.delete(order.id);
353
+
354
+ // Delete all orders (useful for cleanup)
355
+ const deletedCount = await orders.deleteAll();
356
+ console.log(`Deleted ${deletedCount} orders`);
215
357
  ```
216
358
 
217
- ### GPT Conversations
359
+ ### Chat Conversations
218
360
 
219
361
  ```typescript
362
+ type Message = {
363
+ role: 'user' | 'assistant' | 'system';
364
+ content: string;
365
+ timestamp: string;
366
+ };
367
+
220
368
  type Conversation = {
221
369
  title: string;
222
370
  model: string;
223
- messages: Array<{
224
- role: 'user' | 'assistant' | 'system';
225
- content: string;
226
- timestamp: string;
227
- }>;
371
+ messages: Message[];
372
+ metadata?: {
373
+ tokens_used?: number;
374
+ cost?: number;
375
+ };
228
376
  };
229
377
 
230
378
  const conversations = client.userData.collection<Conversation>('chats');
231
379
 
380
+ // Create conversation
232
381
  const conv = await conversations.insert({
233
- title: 'Project Planning',
382
+ title: 'Project Planning Discussion',
234
383
  model: 'claude-3-opus',
235
384
  messages: [
236
385
  {
237
386
  role: 'user',
238
- content: 'Help me plan a new feature',
387
+ content: 'Help me plan a new feature for my app',
239
388
  timestamp: new Date().toISOString(),
240
389
  },
241
390
  ],
242
391
  });
243
392
 
244
- // Add message
245
- const retrieved = await conversations.get(conv.id);
246
- await conversations.patch(conv.id, {
247
- messages: [
248
- ...retrieved!.data.messages,
249
- {
250
- role: 'assistant',
251
- content: 'I can help you with that...',
252
- timestamp: new Date().toISOString(),
253
- },
254
- ],
393
+ // Add assistant response
394
+ const current = await conversations.get(conv.id);
395
+ if (current) {
396
+ await conversations.patch(conv.id, {
397
+ messages: [
398
+ ...current.data.messages,
399
+ {
400
+ role: 'assistant',
401
+ content: 'I can help you with that. What feature are you thinking about?',
402
+ timestamp: new Date().toISOString(),
403
+ },
404
+ ],
405
+ });
406
+ }
407
+
408
+ // List recent conversations
409
+ const recent = await conversations
410
+ .select()
411
+ .order('updated_at', { descending: true })
412
+ .limit(20)
413
+ .execute();
414
+ ```
415
+
416
+ ### User Preferences
417
+
418
+ ```typescript
419
+ type UserPreferences = {
420
+ theme: 'light' | 'dark' | 'auto';
421
+ language: string;
422
+ notifications: {
423
+ email: boolean;
424
+ push: boolean;
425
+ sms: boolean;
426
+ };
427
+ privacy: {
428
+ profile_visible: boolean;
429
+ show_activity: boolean;
430
+ };
431
+ };
432
+
433
+ const prefs = client.userData.collection<UserPreferences>('preferences');
434
+
435
+ // Initialize preferences
436
+ await prefs.insert({
437
+ theme: 'auto',
438
+ language: 'en',
439
+ notifications: {
440
+ email: true,
441
+ push: true,
442
+ sms: false,
443
+ },
444
+ privacy: {
445
+ profile_visible: true,
446
+ show_activity: false,
447
+ },
255
448
  });
449
+
450
+ // Update theme only
451
+ await prefs.patch(prefId, { theme: 'dark' });
256
452
  ```
257
453
 
258
454
  ## UUID Primary Keys
259
455
 
260
- All records use UUID strings as primary keys:
456
+ All records use UUID strings as primary keys.
457
+
458
+ ### Auto-generated UUIDs
261
459
 
262
460
  ```typescript
263
- // Database auto-generates UUID
264
461
  const order = await orders.insert({
265
462
  order_number: 'ORD-001',
266
463
  status: 'pending',
267
464
  });
268
- console.log(order.id); // "550e8400-e29b-41d4-a716-446655440000"
269
-
270
- // Or provide your own UUID
271
- import { randomUUID } from 'crypto';
272
465
 
273
- const customId = randomUUID();
274
- const order2 = await orders.insert(
275
- { order_number: 'ORD-002', status: 'pending' },
276
- customId
277
- );
278
- console.log(order2.id === customId); // true
466
+ console.log(order.id); // "550e8400-e29b-41d4-a716-446655440000"
279
467
  ```
280
468
 
281
- **Client-side UUID generation** (recommended for offline-first apps):
469
+ ### Custom UUIDs
470
+
471
+ Useful for offline-first applications or client-side ID generation:
282
472
 
283
473
  ```typescript
284
474
  // Node.js
@@ -288,29 +478,40 @@ const id = randomUUID();
288
478
  // Browser
289
479
  const id = crypto.randomUUID();
290
480
 
291
- // TypeScript with uuid library
481
+ // With uuid library
292
482
  import { v4 as uuidv4 } from 'uuid';
293
483
  const id = uuidv4();
484
+
485
+ // Use custom ID
486
+ const order = await orders.insert(
487
+ { order_number: 'ORD-002', status: 'pending' },
488
+ id
489
+ );
294
490
  ```
295
491
 
296
492
  ## TypeScript Support
297
493
 
494
+ Full type safety with generic types:
495
+
298
496
  ```typescript
299
- type MyData = {
497
+ type Product = {
300
498
  name: string;
301
- count: number;
499
+ price: number;
500
+ in_stock: boolean;
302
501
  };
303
502
 
304
- const collection = client.userData.collection<MyData>('items');
503
+ const products = client.userData.collection<Product>('products');
305
504
 
306
- const record = await collection.insert({
307
- name: 'Item 1',
308
- count: 42,
505
+ const product = await products.insert({
506
+ name: 'Widget',
507
+ price: 29.99,
508
+ in_stock: true,
309
509
  });
310
510
 
311
511
  // TypeScript knows the shape
312
- console.log(record.data.name); // ✓ OK
313
- console.log(record.data.invalid); // TypeScript error
512
+ console.log(product.data.name); // ✓ string
513
+ console.log(product.data.price); // number
514
+ console.log(product.data.invalid); // ✗ TypeScript error
314
515
  ```
315
516
 
316
517
  ## Error Handling
@@ -322,26 +523,64 @@ try {
322
523
  await collection.insert(data);
323
524
  } catch (error) {
324
525
  if (error instanceof DataServiceError) {
325
- console.error('Code:', error.code);
526
+ console.error('Error code:', error.code);
326
527
  console.error('Message:', error.message);
327
- console.error('Status:', error.statusCode);
328
-
329
- if (error.code === '23505') {
330
- // Unique constraint violation
331
- console.log('Collection name already exists');
528
+ console.error('HTTP status:', error.statusCode);
529
+
530
+ // Handle specific errors
531
+ switch (error.code) {
532
+ case '23505':
533
+ console.log('Duplicate collection name - use a different name');
534
+ break;
535
+ case 'PGRST301':
536
+ console.log('Authentication failed - check your token');
537
+ break;
538
+ default:
539
+ console.log('Unexpected error:', error.message);
332
540
  }
541
+ } else {
542
+ console.error('Unknown error:', error);
333
543
  }
334
544
  }
335
545
  ```
336
546
 
547
+ Common error codes:
548
+ - `23505`: Unique constraint violation (duplicate collection name)
549
+ - `PGRST301`: JWT authentication failed
550
+ - `PGRST204`: No rows returned
551
+ - `PGRST116`: Invalid query syntax
552
+
553
+ ## Security
554
+
555
+ The SDK is built on PostgreSQL Row-Level Security (RLS):
556
+
557
+ - **JWT Authentication**: User identity from JWT token payload
558
+ - **Automatic Isolation**: Each user can only access their own data
559
+ - **No Admin Bypass**: Even database admins respect RLS policies
560
+ - **Secure by Default**: No configuration needed
561
+
562
+ Your JWT token must include a `user_id` claim:
563
+
564
+ ```json
565
+ {
566
+ "user_id": "user_f4ed2364ffcf24d6c6707d5ca5e4fe6d",
567
+ "exp": 1735689600
568
+ }
569
+ ```
570
+
337
571
  ## Development
338
572
 
339
573
  ### Setup
340
574
 
341
575
  ```bash
576
+ # Clone repository
342
577
  git clone https://github.com/seaverse/dataservice
343
578
  cd dataservice
579
+
580
+ # Install dependencies
344
581
  npm install
582
+
583
+ # Build
345
584
  npm run build
346
585
  ```
347
586
 
@@ -351,7 +590,7 @@ npm run build
351
590
  # Copy environment template
352
591
  cp .env.example .env
353
592
 
354
- # Edit .env with your credentials
593
+ # Configure your credentials
355
594
  # POSTGREST_URL=https://your-api.example.com
356
595
  # JWT_TOKEN=Bearer your-token-here
357
596
 
@@ -364,26 +603,15 @@ npm run test:examples
364
603
  - `npm run build` - Build the SDK
365
604
  - `npm run dev` - Build in watch mode
366
605
  - `npm run test:examples` - Run integration tests
367
- - `npm run lint` - Lint code
368
- - `npm run format` - Format code
369
-
370
- ## Security
371
-
372
- Built on PostgreSQL Row-Level Security (RLS):
373
- - JWT authentication with user_id in payload
374
- - Automatic data isolation per user
375
- - No admin bypass
376
-
377
- ## Contributing
378
-
379
- See [CONTRIBUTING.md](CONTRIBUTING.md)
606
+ - `npm run lint` - Lint code with ESLint
607
+ - `npm run format` - Format code with Prettier
380
608
 
381
609
  ## License
382
610
 
383
- MIT - See [LICENSE](LICENSE)
611
+ MIT
384
612
 
385
613
  ## Links
386
614
 
387
- - [GitHub](https://github.com/seaverse/dataservice)
388
- - [Issues](https://github.com/seaverse/dataservice/issues)
389
- - [AI Usage Guide](AI-USAGE-GUIDE.md)
615
+ - [GitHub Repository](https://github.com/seaverse/dataservice)
616
+ - [Report Issues](https://github.com/seaverse/dataservice/issues)
617
+ - [SeaVerse Platform](https://seaverse.ai)
package/dist/index.d.mts CHANGED
@@ -12,14 +12,13 @@
12
12
  * @example
13
13
  * ```typescript
14
14
  * const config: ClientConfig = {
15
- * url: 'https://your-postgrest-api.example.com',
16
15
  * token: 'Bearer your-jwt-token-here',
17
16
  * };
18
17
  * ```
19
18
  */
20
19
  interface ClientConfig {
21
- /** PostgREST API base URL */
22
- url: string;
20
+ /** PostgREST API base URL (default: https://dataservice-api.seaverse.ai) */
21
+ url?: string;
23
22
  /** JWT token containing user_id in payload.user_id */
24
23
  token: string;
25
24
  /** Optional configuration */
@@ -196,6 +195,8 @@ interface Collection<T = any> {
196
195
  patch(id: string, partial: Partial<T>): Promise<DataRecord<T>>;
197
196
  /** Hard delete a record by ID */
198
197
  delete(id: string): Promise<void>;
198
+ /** Hard delete all records in this collection */
199
+ deleteAll(): Promise<number>;
199
200
  /** Soft delete a record by ID (sets deleted_at) */
200
201
  softDelete(id: string): Promise<boolean>;
201
202
  /** Restore a soft-deleted record */
@@ -250,16 +251,27 @@ interface DataServiceClient {
250
251
  *
251
252
  * AI-friendly: Single entry point with clear configuration
252
253
  *
253
- * @param config - Client configuration with URL and JWT token
254
+ * @param config - Client configuration with JWT token (URL defaults to https://dataservice-api.seaverse.ai)
254
255
  * @returns DataServiceClient instance
255
256
  *
256
257
  * @example
257
258
  * ```typescript
259
+ * // Use default SeaVerse API endpoint (Bearer prefix auto-added)
260
+ * const client = createClient({
261
+ * token: 'your-jwt-token-here',
262
+ * });
263
+ *
264
+ * // Or with explicit Bearer prefix (both work)
258
265
  * const client = createClient({
259
- * url: 'https://your-postgrest-api.example.com',
260
266
  * token: 'Bearer your-jwt-token-here',
261
267
  * });
262
268
  *
269
+ * // Or specify custom endpoint
270
+ * const client = createClient({
271
+ * url: 'https://your-postgrest-api.example.com',
272
+ * token: 'your-jwt-token-here',
273
+ * });
274
+ *
263
275
  * // appId is automatically extracted from current URL
264
276
  * // Direct access to collections - clean and simple
265
277
  * const order = await client.userData.collection('orders').insert({ ... });
@@ -277,15 +289,15 @@ declare function createClient(config: ClientConfig): DataServiceClient;
277
289
  *
278
290
  * @example Basic Usage
279
291
  * ```typescript
280
- * import { createClient } from '@seaverse/data-service-sdk';
292
+ * import { createClient } from '@seaverse/dataservice';
281
293
  *
282
- * // appId is automatically extracted from current URL
294
+ * // Create client (Bearer prefix auto-added, uses default SeaVerse API)
283
295
  * const client = createClient({
284
- * url: 'https://postgrest.example.com',
285
296
  * token: 'your-jwt-token',
286
297
  * });
287
298
  *
288
- * // Insert data - appId is automatically included
299
+ * // appId is automatically extracted from current URL
300
+ * // Insert data
289
301
  * const order = await client.userData.collection('orders').insert({
290
302
  * order_number: 'ORD-123',
291
303
  * status: 'pending',
@@ -296,7 +308,7 @@ declare function createClient(config: ClientConfig): DataServiceClient;
296
308
  * const orders = await client.userData
297
309
  * .collection('orders')
298
310
  * .select()
299
- * .eq('status', 'pending')
311
+ * .eq('data->>status', 'pending')
300
312
  * .order('created_at', { descending: true })
301
313
  * .limit(10)
302
314
  * .execute();
package/dist/index.d.ts CHANGED
@@ -12,14 +12,13 @@
12
12
  * @example
13
13
  * ```typescript
14
14
  * const config: ClientConfig = {
15
- * url: 'https://your-postgrest-api.example.com',
16
15
  * token: 'Bearer your-jwt-token-here',
17
16
  * };
18
17
  * ```
19
18
  */
20
19
  interface ClientConfig {
21
- /** PostgREST API base URL */
22
- url: string;
20
+ /** PostgREST API base URL (default: https://dataservice-api.seaverse.ai) */
21
+ url?: string;
23
22
  /** JWT token containing user_id in payload.user_id */
24
23
  token: string;
25
24
  /** Optional configuration */
@@ -196,6 +195,8 @@ interface Collection<T = any> {
196
195
  patch(id: string, partial: Partial<T>): Promise<DataRecord<T>>;
197
196
  /** Hard delete a record by ID */
198
197
  delete(id: string): Promise<void>;
198
+ /** Hard delete all records in this collection */
199
+ deleteAll(): Promise<number>;
199
200
  /** Soft delete a record by ID (sets deleted_at) */
200
201
  softDelete(id: string): Promise<boolean>;
201
202
  /** Restore a soft-deleted record */
@@ -250,16 +251,27 @@ interface DataServiceClient {
250
251
  *
251
252
  * AI-friendly: Single entry point with clear configuration
252
253
  *
253
- * @param config - Client configuration with URL and JWT token
254
+ * @param config - Client configuration with JWT token (URL defaults to https://dataservice-api.seaverse.ai)
254
255
  * @returns DataServiceClient instance
255
256
  *
256
257
  * @example
257
258
  * ```typescript
259
+ * // Use default SeaVerse API endpoint (Bearer prefix auto-added)
260
+ * const client = createClient({
261
+ * token: 'your-jwt-token-here',
262
+ * });
263
+ *
264
+ * // Or with explicit Bearer prefix (both work)
258
265
  * const client = createClient({
259
- * url: 'https://your-postgrest-api.example.com',
260
266
  * token: 'Bearer your-jwt-token-here',
261
267
  * });
262
268
  *
269
+ * // Or specify custom endpoint
270
+ * const client = createClient({
271
+ * url: 'https://your-postgrest-api.example.com',
272
+ * token: 'your-jwt-token-here',
273
+ * });
274
+ *
263
275
  * // appId is automatically extracted from current URL
264
276
  * // Direct access to collections - clean and simple
265
277
  * const order = await client.userData.collection('orders').insert({ ... });
@@ -277,15 +289,15 @@ declare function createClient(config: ClientConfig): DataServiceClient;
277
289
  *
278
290
  * @example Basic Usage
279
291
  * ```typescript
280
- * import { createClient } from '@seaverse/data-service-sdk';
292
+ * import { createClient } from '@seaverse/dataservice';
281
293
  *
282
- * // appId is automatically extracted from current URL
294
+ * // Create client (Bearer prefix auto-added, uses default SeaVerse API)
283
295
  * const client = createClient({
284
- * url: 'https://postgrest.example.com',
285
296
  * token: 'your-jwt-token',
286
297
  * });
287
298
  *
288
- * // Insert data - appId is automatically included
299
+ * // appId is automatically extracted from current URL
300
+ * // Insert data
289
301
  * const order = await client.userData.collection('orders').insert({
290
302
  * order_number: 'ORD-123',
291
303
  * status: 'pending',
@@ -296,7 +308,7 @@ declare function createClient(config: ClientConfig): DataServiceClient;
296
308
  * const orders = await client.userData
297
309
  * .collection('orders')
298
310
  * .select()
299
- * .eq('status', 'pending')
311
+ * .eq('data->>status', 'pending')
300
312
  * .order('created_at', { descending: true })
301
313
  * .limit(10)
302
314
  * .execute();
package/dist/index.js CHANGED
@@ -57,11 +57,12 @@ var HTTPClient = class {
57
57
  fetchFn;
58
58
  timeout;
59
59
  constructor(config) {
60
- this.baseUrl = config.url.replace(/\/$/, "");
60
+ this.baseUrl = (config.url || "https://dataservice-api.seaverse.ai").replace(/\/$/, "");
61
61
  this.fetchFn = config.options?.fetch || globalThis.fetch;
62
62
  this.timeout = config.options?.timeout || 3e4;
63
+ const token = config.token.startsWith("Bearer ") ? config.token : `Bearer ${config.token}`;
63
64
  this.headers = {
64
- "Authorization": config.token,
65
+ "Authorization": token,
65
66
  "Content-Type": "application/json",
66
67
  "Prefer": "return=representation",
67
68
  ...config.options?.headers
@@ -285,6 +286,18 @@ var CollectionImpl = class {
285
286
  `/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
286
287
  );
287
288
  }
289
+ async deleteAll() {
290
+ const records = await this.client.get(
291
+ `/user_data?app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
292
+ );
293
+ const count = records.length;
294
+ if (count > 0) {
295
+ await this.client.delete(
296
+ `/user_data?app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
297
+ );
298
+ }
299
+ return count;
300
+ }
288
301
  async softDelete(id) {
289
302
  const response = await this.client.post("/rpc/soft_delete_user_data", {
290
303
  p_id: id
package/dist/index.mjs CHANGED
@@ -29,11 +29,12 @@ var HTTPClient = class {
29
29
  fetchFn;
30
30
  timeout;
31
31
  constructor(config) {
32
- this.baseUrl = config.url.replace(/\/$/, "");
32
+ this.baseUrl = (config.url || "https://dataservice-api.seaverse.ai").replace(/\/$/, "");
33
33
  this.fetchFn = config.options?.fetch || globalThis.fetch;
34
34
  this.timeout = config.options?.timeout || 3e4;
35
+ const token = config.token.startsWith("Bearer ") ? config.token : `Bearer ${config.token}`;
35
36
  this.headers = {
36
- "Authorization": config.token,
37
+ "Authorization": token,
37
38
  "Content-Type": "application/json",
38
39
  "Prefer": "return=representation",
39
40
  ...config.options?.headers
@@ -257,6 +258,18 @@ var CollectionImpl = class {
257
258
  `/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
258
259
  );
259
260
  }
261
+ async deleteAll() {
262
+ const records = await this.client.get(
263
+ `/user_data?app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
264
+ );
265
+ const count = records.length;
266
+ if (count > 0) {
267
+ await this.client.delete(
268
+ `/user_data?app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
269
+ );
270
+ }
271
+ return count;
272
+ }
260
273
  async softDelete(id) {
261
274
  const response = await this.client.post("/rpc/soft_delete_user_data", {
262
275
  p_id: id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seaverse/dataservice",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "AI-Friendly Universal Data Storage SDK for TypeScript/JavaScript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -35,13 +35,12 @@
35
35
  "license": "MIT",
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "https://github.com/seaverseai/sv-sdk.git",
39
- "directory": "packages/dataservice"
38
+ "url": "https://github.com/seaverse/dataservice.git"
40
39
  },
41
40
  "bugs": {
42
- "url": "https://github.com/seaverseai/sv-sdk/issues"
41
+ "url": "https://github.com/seaverse/dataservice/issues"
43
42
  },
44
- "homepage": "https://github.com/seaverseai/sv-sdk/tree/main/packages/dataservice#readme",
43
+ "homepage": "https://github.com/seaverse/dataservice#readme",
45
44
  "devDependencies": {
46
45
  "@types/node": "^20.11.0",
47
46
  "@typescript-eslint/eslint-plugin": "^6.19.0",