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