@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 +391 -163
- package/dist/index.d.mts +22 -10
- package/dist/index.d.ts +22 -10
- package/dist/index.js +15 -2
- package/dist/index.mjs +15 -2
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
#
|
|
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
|
-
//
|
|
25
|
+
// Create client (Bearer prefix auto-added, uses default SeaVerse API, appId auto-extracted from URL)
|
|
17
26
|
const client = createClient({
|
|
18
|
-
|
|
19
|
-
token: 'Bearer your-jwt-token-here',
|
|
27
|
+
token: 'your-jwt-token',
|
|
20
28
|
});
|
|
21
29
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
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
|
-
//
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
//
|
|
51
|
-
await
|
|
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
|
-
|
|
62
|
+
DataServiceClient
|
|
63
|
+
└── DataTable (e.g., userData)
|
|
64
|
+
└── Collection (e.g., "orders")
|
|
65
|
+
└── DataRecord (JSONB data + metadata)
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
### Application ID
|
|
68
|
+
### Application ID
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
The SDK automatically extracts `app_id` from the current environment:
|
|
67
71
|
|
|
72
|
+
**Browser**: Extracted from URL hostname
|
|
68
73
|
```
|
|
69
|
-
|
|
70
|
-
→ appId: app_8e5e867e-
|
|
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
|
|
78
|
+
**Node.js**: Set via environment variable
|
|
77
79
|
```bash
|
|
78
80
|
export SEAVERSE_APP_ID=my-app-id
|
|
79
81
|
```
|
|
80
82
|
|
|
81
|
-
|
|
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
|
-
|
|
95
|
+
### Collections
|
|
84
96
|
|
|
85
|
-
|
|
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('
|
|
90
|
-
await client.userData.collection('
|
|
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
|
-
|
|
93
|
-
await client.userData.batchInsert('orders', [order1, order2]);
|
|
115
|
+
### Data Records
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
137
|
+
import { createClient } from '@seaverse/dataservice';
|
|
106
138
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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;
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
132
|
-
collection.insert(data: T
|
|
187
|
+
// Insert with auto-generated UUID
|
|
188
|
+
collection.insert(data: T): Promise<DataRecord<T>>
|
|
133
189
|
|
|
134
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
.
|
|
160
|
-
.
|
|
161
|
-
.
|
|
162
|
-
.
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
client.
|
|
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<{
|
|
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: [
|
|
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:
|
|
327
|
+
total: 109.97,
|
|
203
328
|
});
|
|
204
329
|
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
336
|
+
.gt('data->total', '50')
|
|
210
337
|
.order('created_at', { descending: true })
|
|
338
|
+
.limit(10)
|
|
211
339
|
.execute();
|
|
212
340
|
|
|
213
|
-
|
|
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
|
-
###
|
|
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:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
497
|
+
type Product = {
|
|
300
498
|
name: string;
|
|
301
|
-
|
|
499
|
+
price: number;
|
|
500
|
+
in_stock: boolean;
|
|
302
501
|
};
|
|
303
502
|
|
|
304
|
-
const
|
|
503
|
+
const products = client.userData.collection<Product>('products');
|
|
305
504
|
|
|
306
|
-
const
|
|
307
|
-
name: '
|
|
308
|
-
|
|
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(
|
|
313
|
-
console.log(
|
|
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('
|
|
526
|
+
console.error('Error code:', error.code);
|
|
326
527
|
console.error('Message:', error.message);
|
|
327
|
-
console.error('
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
-
- [
|
|
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
|
|
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
|
|
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/
|
|
292
|
+
* import { createClient } from '@seaverse/dataservice';
|
|
281
293
|
*
|
|
282
|
-
* //
|
|
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
|
-
* //
|
|
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
|
|
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
|
|
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/
|
|
292
|
+
* import { createClient } from '@seaverse/dataservice';
|
|
281
293
|
*
|
|
282
|
-
* //
|
|
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
|
-
* //
|
|
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":
|
|
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":
|
|
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.
|
|
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/
|
|
39
|
-
"directory": "packages/dataservice"
|
|
38
|
+
"url": "https://github.com/seaverse/dataservice.git"
|
|
40
39
|
},
|
|
41
40
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/seaverse/dataservice/issues"
|
|
43
42
|
},
|
|
44
|
-
"homepage": "https://github.com/
|
|
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",
|