@seaverse/dataservice 1.3.0 → 1.5.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 +244 -86
- package/dist/index.d.mts +46 -24
- package/dist/index.d.ts +46 -24
- package/dist/index.js +88 -34
- package/dist/index.mjs +86 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,36 +22,46 @@ npm install @seaverse/dataservice
|
|
|
22
22
|
```typescript
|
|
23
23
|
import { createClient } from '@seaverse/dataservice';
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
const client = createClient({
|
|
25
|
+
// Option 1: Auto-fetch token from parent page (iframe only)
|
|
26
|
+
const client = await createClient({});
|
|
27
|
+
|
|
28
|
+
// Option 2: Provide token explicitly
|
|
29
|
+
const client = await createClient({
|
|
27
30
|
token: 'your-jwt-token',
|
|
28
31
|
});
|
|
29
32
|
|
|
30
|
-
//
|
|
31
|
-
const
|
|
33
|
+
// Store user preferences (single record)
|
|
34
|
+
const userPrefs = client.userData.collection('user_preferences');
|
|
35
|
+
await userPrefs.insert({
|
|
36
|
+
theme: 'dark',
|
|
37
|
+
language: 'en',
|
|
38
|
+
notifications: true,
|
|
39
|
+
});
|
|
32
40
|
|
|
33
|
-
//
|
|
34
|
-
|
|
41
|
+
// Update preferences
|
|
42
|
+
await userPrefs.patch(userPrefs.id, { theme: 'light' });
|
|
43
|
+
|
|
44
|
+
// Store multiple items (e.g., orders) - each needs unique collection name
|
|
45
|
+
const order1 = await client.userData.collection('order_001').insert({
|
|
35
46
|
order_number: 'ORD-001',
|
|
36
47
|
status: 'pending',
|
|
37
48
|
total: 99.99,
|
|
38
49
|
});
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
.execute();
|
|
46
|
-
|
|
47
|
-
// Update data
|
|
48
|
-
await orders.patch(order.id, { status: 'completed' });
|
|
51
|
+
const order2 = await client.userData.collection('order_002').insert({
|
|
52
|
+
order_number: 'ORD-002',
|
|
53
|
+
status: 'shipped',
|
|
54
|
+
total: 149.99,
|
|
55
|
+
});
|
|
49
56
|
|
|
50
|
-
//
|
|
51
|
-
await
|
|
57
|
+
// Or use batch insert for multiple records
|
|
58
|
+
const orders = await client.userData.batchInsert('order', [
|
|
59
|
+
{ order_number: 'ORD-003', status: 'pending', total: 79.99 },
|
|
60
|
+
{ order_number: 'ORD-004', status: 'pending', total: 199.99 },
|
|
61
|
+
]);
|
|
52
62
|
|
|
53
|
-
//
|
|
54
|
-
|
|
63
|
+
// Delete a specific record
|
|
64
|
+
await client.userData.collection('order_001').delete(order1.id);
|
|
55
65
|
```
|
|
56
66
|
|
|
57
67
|
## Core Concepts
|
|
@@ -94,22 +104,144 @@ The SDK provides access to different data tables with different permission scope
|
|
|
94
104
|
|
|
95
105
|
### Collections
|
|
96
106
|
|
|
97
|
-
|
|
107
|
+
**Critical Understanding**: A collection name identifies a **single record**, not a container for multiple records.
|
|
108
|
+
|
|
109
|
+
The `(user_id, app_id, collection_name)` combination is a **unique constraint** - meaning each collection name can only store ONE record per user and app.
|
|
110
|
+
|
|
111
|
+
**To store multiple records, you MUST use unique collection names**:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// ✓ CORRECT: Each record gets a unique collection name
|
|
115
|
+
await client.userData.collection('order_001').insert(order1);
|
|
116
|
+
await client.userData.collection('order_002').insert(order2);
|
|
117
|
+
await client.userData.collection('order_003').insert(order3);
|
|
118
|
+
|
|
119
|
+
// ✓ RECOMMENDED: Use batch insert (auto-generates unique names)
|
|
120
|
+
const insertedOrders = await client.userData.batchInsert('order', [order1, order2, order3]);
|
|
121
|
+
// Creates collections: order_<timestamp>_0, order_<timestamp>_1, order_<timestamp>_2
|
|
122
|
+
|
|
123
|
+
// ✗ WRONG: Reusing the same collection name
|
|
124
|
+
const orders = client.userData.collection('orders');
|
|
125
|
+
await orders.insert(order1); // ✓ Success
|
|
126
|
+
await orders.insert(order2); // ✗ ERROR: 409 Conflict (collection 'orders' already exists)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Why this design?**
|
|
130
|
+
- Each collection name acts as a **unique key** for a single record
|
|
131
|
+
- Think of it like a key-value store: `collection_name` → single record
|
|
132
|
+
- For multiple records, use patterns like: `order_${orderId}`, `msg_${conversationId}_${msgId}`
|
|
133
|
+
|
|
134
|
+
## ⚠️ Common Pitfalls
|
|
135
|
+
|
|
136
|
+
### Pitfall #1: Treating Collections as Containers
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// ❌ WRONG: This looks like it should work, but it doesn't
|
|
140
|
+
const orders = client.userData.collection('orders');
|
|
141
|
+
await orders.insert({ order_number: 'ORD-001', total: 99.99 }); // ✓ Works
|
|
142
|
+
await orders.insert({ order_number: 'ORD-002', total: 149.99 }); // ✗ ERROR: 409 Conflict!
|
|
143
|
+
|
|
144
|
+
// ✓ CORRECT: Each record needs a unique collection name
|
|
145
|
+
await client.userData.collection('order_001').insert({ order_number: 'ORD-001', total: 99.99 });
|
|
146
|
+
await client.userData.collection('order_002').insert({ order_number: 'ORD-002', total: 149.99 });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Why?** The `(user_id, app_id, collection_name)` combination is a unique constraint. Once you insert into `'orders'`, that collection name is "taken" for that user and app.
|
|
150
|
+
|
|
151
|
+
### Pitfall #2: Expecting Query Methods to Return Multiple Records
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// ❌ MISLEADING: This looks like it queries multiple orders
|
|
155
|
+
const orders = client.userData.collection('orders');
|
|
156
|
+
const pending = await orders.select().eq('data->>status', 'pending').execute();
|
|
157
|
+
// Returns: [] or [single order] - NOT multiple orders!
|
|
158
|
+
|
|
159
|
+
// ✓ CORRECT: To work with multiple records, track them separately
|
|
160
|
+
const orderIds = ['order_001', 'order_002', 'order_003'];
|
|
161
|
+
const allOrders = await Promise.all(
|
|
162
|
+
orderIds.map(id => client.userData.collection(id).get(recordId))
|
|
163
|
+
);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Why?** A collection name identifies a single record, so queries on that collection can only return 0 or 1 record.
|
|
167
|
+
|
|
168
|
+
### Pitfall #3: Using Plural Names for Collections
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// ❌ MISLEADING: Plural name suggests multiple items
|
|
172
|
+
const orders = client.userData.collection('orders');
|
|
173
|
+
const users = client.userData.collection('users');
|
|
174
|
+
|
|
175
|
+
// ✓ BETTER: Use singular or ID-based names
|
|
176
|
+
const order = client.userData.collection('order_12345');
|
|
177
|
+
const userProfile = client.userData.collection('user_profile');
|
|
178
|
+
const preference = client.userData.collection('user_preferences');
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Why?** Plural names create false expectations. Use singular names or include IDs to make it clear each collection is one record.
|
|
182
|
+
|
|
183
|
+
## 💡 Best Practices
|
|
184
|
+
|
|
185
|
+
### Pattern 1: Single Record per Concept
|
|
186
|
+
|
|
187
|
+
Use this for user preferences, settings, profiles - things where you only need one record:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// User preferences (one per user)
|
|
191
|
+
const prefs = await client.userData.collection('preferences').insert({
|
|
192
|
+
theme: 'dark',
|
|
193
|
+
language: 'en',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// User profile (one per user)
|
|
197
|
+
const profile = await client.userData.collection('profile').insert({
|
|
198
|
+
name: 'John Doe',
|
|
199
|
+
avatar: 'https://...',
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Pattern 2: ID-Based Collection Names
|
|
204
|
+
|
|
205
|
+
Use this for multiple items of the same type:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// Multiple orders - each with unique collection name
|
|
209
|
+
const order1 = await client.userData.collection(`order_${orderId1}`).insert(orderData1);
|
|
210
|
+
const order2 = await client.userData.collection(`order_${orderId2}`).insert(orderData2);
|
|
211
|
+
|
|
212
|
+
// Multiple conversations
|
|
213
|
+
const conv1 = await client.userData.collection(`conv_${convId1}`).insert(convData1);
|
|
214
|
+
const conv2 = await client.userData.collection(`conv_${convId2}`).insert(convData2);
|
|
215
|
+
```
|
|
98
216
|
|
|
99
|
-
|
|
217
|
+
### Pattern 3: Batch Insert for Multiple Records
|
|
218
|
+
|
|
219
|
+
Use this when creating multiple records at once:
|
|
100
220
|
|
|
101
221
|
```typescript
|
|
102
|
-
//
|
|
103
|
-
await client.userData.
|
|
104
|
-
|
|
222
|
+
// Create multiple orders in one call
|
|
223
|
+
const orders = await client.userData.batchInsert('order', [
|
|
224
|
+
{ order_number: 'ORD-001', total: 99.99 },
|
|
225
|
+
{ order_number: 'ORD-002', total: 149.99 },
|
|
226
|
+
{ order_number: 'ORD-003', total: 199.99 },
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
// Returns array of DataRecord with auto-generated collection names
|
|
230
|
+
// order_<timestamp>_0, order_<timestamp>_1, order_<timestamp>_2
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Pattern 4: Hierarchical Collection Names
|
|
105
234
|
|
|
106
|
-
|
|
107
|
-
await client.userData.batchInsert('order', [order1, order2]);
|
|
108
|
-
// Creates: order_0, order_1, order_2, ...
|
|
235
|
+
Use this for nested data structures:
|
|
109
236
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await client.userData.collection(
|
|
237
|
+
```typescript
|
|
238
|
+
// Messages within a conversation
|
|
239
|
+
await client.userData.collection(`conv_${convId}_msg_1`).insert(message1);
|
|
240
|
+
await client.userData.collection(`conv_${convId}_msg_2`).insert(message2);
|
|
241
|
+
|
|
242
|
+
// Tasks within a project
|
|
243
|
+
await client.userData.collection(`project_${projId}_task_1`).insert(task1);
|
|
244
|
+
await client.userData.collection(`project_${projId}_task_2`).insert(task2);
|
|
113
245
|
```
|
|
114
246
|
|
|
115
247
|
### Data Records
|
|
@@ -136,31 +268,52 @@ interface DataRecord<T> {
|
|
|
136
268
|
```typescript
|
|
137
269
|
import { createClient } from '@seaverse/dataservice';
|
|
138
270
|
|
|
139
|
-
const client = createClient({
|
|
140
|
-
token
|
|
271
|
+
const client = await createClient({
|
|
272
|
+
token?: string; // JWT token (optional, auto-fetched from parent if in iframe)
|
|
141
273
|
url?: string; // PostgREST API URL (default: https://dataservice-api.seaverse.ai)
|
|
142
274
|
options?: {
|
|
143
275
|
timeout?: number; // Request timeout in ms (default: 30000)
|
|
276
|
+
tokenFetchTimeout?: number; // Token fetch timeout in ms (default: 5000)
|
|
144
277
|
headers?: Record<string, string>; // Additional headers
|
|
145
278
|
};
|
|
146
279
|
});
|
|
147
280
|
```
|
|
148
281
|
|
|
149
|
-
**
|
|
282
|
+
**Token Options:**
|
|
283
|
+
|
|
284
|
+
The SDK supports two ways to provide authentication:
|
|
285
|
+
|
|
286
|
+
1. **Auto-fetch from parent page (iframe only)**: When running in an iframe, the SDK can automatically request the token from the parent page via PostMessage:
|
|
150
287
|
|
|
151
288
|
```typescript
|
|
152
|
-
//
|
|
153
|
-
const client = createClient({
|
|
289
|
+
// No token needed - auto-fetches from parent
|
|
290
|
+
const client = await createClient({});
|
|
291
|
+
|
|
292
|
+
// Parent page should respond to PostMessage:
|
|
293
|
+
// Send: { type: 'seaverse:get_token' }
|
|
294
|
+
// Receive: { type: 'seaverse:token', payload: { accessToken: string, expiresIn: number } }
|
|
295
|
+
// Error: { type: 'seaverse:error', error: string }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
2. **Explicit token**: Provide the token directly (Bearer prefix is auto-added):
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Token without Bearer prefix
|
|
302
|
+
const client = await createClient({
|
|
154
303
|
token: 'your-jwt-token',
|
|
155
304
|
});
|
|
156
305
|
|
|
157
306
|
// Or with explicit Bearer prefix (both work)
|
|
158
|
-
const client = createClient({
|
|
307
|
+
const client = await createClient({
|
|
159
308
|
token: 'Bearer your-jwt-token',
|
|
160
309
|
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Custom Endpoint:**
|
|
161
313
|
|
|
314
|
+
```typescript
|
|
162
315
|
// Use custom API endpoint
|
|
163
|
-
const client = createClient({
|
|
316
|
+
const client = await createClient({
|
|
164
317
|
url: 'https://your-custom-api.example.com',
|
|
165
318
|
token: 'your-jwt-token',
|
|
166
319
|
});
|
|
@@ -221,17 +374,8 @@ collection.patch(id: string, partial: Partial<T>): Promise<DataRecord<T>>
|
|
|
221
374
|
#### Delete
|
|
222
375
|
|
|
223
376
|
```typescript
|
|
224
|
-
//
|
|
377
|
+
// Delete a record by ID (permanent deletion)
|
|
225
378
|
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)
|
|
231
|
-
collection.softDelete(id: string): Promise<boolean>
|
|
232
|
-
|
|
233
|
-
// Restore soft-deleted record
|
|
234
|
-
collection.restore(id: string): Promise<boolean>
|
|
235
379
|
```
|
|
236
380
|
|
|
237
381
|
### Query Builder
|
|
@@ -313,11 +457,10 @@ const client = createClient({
|
|
|
313
457
|
token: process.env.JWT_TOKEN!,
|
|
314
458
|
});
|
|
315
459
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
order_number: `ORD-${Date.now()}`,
|
|
460
|
+
// Create multiple orders - each with unique collection name
|
|
461
|
+
const orderNumber1 = `ORD-${Date.now()}`;
|
|
462
|
+
const order1 = await client.userData.collection<Order>(`order_${orderNumber1}`).insert({
|
|
463
|
+
order_number: orderNumber1,
|
|
321
464
|
customer_email: 'customer@example.com',
|
|
322
465
|
items: [
|
|
323
466
|
{ product_id: 'PROD-001', quantity: 2, price: 29.99 },
|
|
@@ -327,33 +470,36 @@ const order = await orders.insert({
|
|
|
327
470
|
total: 109.97,
|
|
328
471
|
});
|
|
329
472
|
|
|
330
|
-
console.log('Order created:',
|
|
331
|
-
|
|
332
|
-
// Query pending orders over $50
|
|
333
|
-
const pendingOrders = await orders
|
|
334
|
-
.select()
|
|
335
|
-
.eq('data->>status', 'pending')
|
|
336
|
-
.gt('data->total', '50')
|
|
337
|
-
.order('created_at', { descending: true })
|
|
338
|
-
.limit(10)
|
|
339
|
-
.execute();
|
|
340
|
-
|
|
341
|
-
console.log(`Found ${pendingOrders.length} pending orders`);
|
|
342
|
-
|
|
343
|
-
// Update order status
|
|
344
|
-
await orders.patch(order.id, { status: 'shipped' });
|
|
473
|
+
console.log('Order created:', order1.id, 'Collection:', `order_${orderNumber1}`);
|
|
345
474
|
|
|
346
|
-
//
|
|
347
|
-
const
|
|
475
|
+
// Create another order
|
|
476
|
+
const orderNumber2 = `ORD-${Date.now() + 1}`;
|
|
477
|
+
const order2 = await client.userData.collection<Order>(`order_${orderNumber2}`).insert({
|
|
478
|
+
order_number: orderNumber2,
|
|
348
479
|
customer_email: 'customer@example.com',
|
|
480
|
+
items: [
|
|
481
|
+
{ product_id: 'PROD-003', quantity: 1, price: 199.99 },
|
|
482
|
+
],
|
|
483
|
+
status: 'pending',
|
|
484
|
+
total: 199.99,
|
|
349
485
|
});
|
|
350
486
|
|
|
487
|
+
// Update order status (access by collection name)
|
|
488
|
+
await client.userData.collection(`order_${orderNumber1}`).patch(order1.id, {
|
|
489
|
+
status: 'shipped'
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Get specific order
|
|
493
|
+
const retrieved = await client.userData.collection(`order_${orderNumber1}`).get(order1.id);
|
|
494
|
+
console.log('Order status:', retrieved?.data.status);
|
|
495
|
+
|
|
351
496
|
// Delete a specific order
|
|
352
|
-
await
|
|
497
|
+
await client.userData.collection(`order_${orderNumber1}`).delete(order1.id);
|
|
353
498
|
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
499
|
+
// Note: To query across multiple orders, you would need to:
|
|
500
|
+
// 1. Store order metadata in a separate tracking collection
|
|
501
|
+
// 2. Or use a naming convention and iterate through known order IDs
|
|
502
|
+
// 3. Or use the batchInsert pattern and track the generated collection names
|
|
357
503
|
```
|
|
358
504
|
|
|
359
505
|
### Chat Conversations
|
|
@@ -375,10 +521,9 @@ type Conversation = {
|
|
|
375
521
|
};
|
|
376
522
|
};
|
|
377
523
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const conv = await conversations.insert({
|
|
524
|
+
// Create a new conversation with unique ID
|
|
525
|
+
const conversationId = `conv_${Date.now()}`;
|
|
526
|
+
const conv = await client.userData.collection<Conversation>(conversationId).insert({
|
|
382
527
|
title: 'Project Planning Discussion',
|
|
383
528
|
model: 'claude-3-opus',
|
|
384
529
|
messages: [
|
|
@@ -390,10 +535,12 @@ const conv = await conversations.insert({
|
|
|
390
535
|
],
|
|
391
536
|
});
|
|
392
537
|
|
|
393
|
-
|
|
394
|
-
|
|
538
|
+
console.log('Conversation created:', conversationId);
|
|
539
|
+
|
|
540
|
+
// Add assistant response to existing conversation
|
|
541
|
+
const current = await client.userData.collection<Conversation>(conversationId).get(conv.id);
|
|
395
542
|
if (current) {
|
|
396
|
-
await
|
|
543
|
+
await client.userData.collection<Conversation>(conversationId).patch(conv.id, {
|
|
397
544
|
messages: [
|
|
398
545
|
...current.data.messages,
|
|
399
546
|
{
|
|
@@ -405,12 +552,23 @@ if (current) {
|
|
|
405
552
|
});
|
|
406
553
|
}
|
|
407
554
|
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.
|
|
555
|
+
// Store individual messages separately (alternative pattern)
|
|
556
|
+
// Each message gets its own collection
|
|
557
|
+
const msgId1 = await client.userData.collection(`${conversationId}_msg_1`).insert({
|
|
558
|
+
role: 'user',
|
|
559
|
+
content: 'Help me plan a new feature',
|
|
560
|
+
timestamp: new Date().toISOString(),
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const msgId2 = await client.userData.collection(`${conversationId}_msg_2`).insert({
|
|
564
|
+
role: 'assistant',
|
|
565
|
+
content: 'I can help with that!',
|
|
566
|
+
timestamp: new Date().toISOString(),
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Get a specific conversation
|
|
570
|
+
const retrieved = await client.userData.collection(conversationId).get(conv.id);
|
|
571
|
+
console.log('Conversation title:', retrieved?.data.title);
|
|
414
572
|
```
|
|
415
573
|
|
|
416
574
|
### User Preferences
|
package/dist/index.d.mts
CHANGED
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
interface ClientConfig {
|
|
20
20
|
/** PostgREST API base URL (default: https://dataservice-api.seaverse.ai) */
|
|
21
21
|
url?: string;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* JWT token containing user_id in payload.user_id
|
|
24
|
+
* If not provided, will attempt to fetch from parent page via PostMessage (iframe only)
|
|
25
|
+
*/
|
|
26
|
+
token?: string;
|
|
24
27
|
/** Optional configuration */
|
|
25
28
|
options?: {
|
|
26
29
|
/** Custom fetch implementation (useful for Node.js < 18) */
|
|
@@ -29,6 +32,8 @@ interface ClientConfig {
|
|
|
29
32
|
headers?: Record<string, string>;
|
|
30
33
|
/** Request timeout in milliseconds (default: 30000) */
|
|
31
34
|
timeout?: number;
|
|
35
|
+
/** Timeout for fetching token from parent (milliseconds, default: 5000) */
|
|
36
|
+
tokenFetchTimeout?: number;
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
@@ -193,14 +198,8 @@ interface Collection<T = any> {
|
|
|
193
198
|
update(id: string, data: T): Promise<DataRecord<T>>;
|
|
194
199
|
/** Patch a record by ID (merges with existing data) */
|
|
195
200
|
patch(id: string, partial: Partial<T>): Promise<DataRecord<T>>;
|
|
196
|
-
/**
|
|
201
|
+
/** Delete a record by ID (permanent) */
|
|
197
202
|
delete(id: string): Promise<void>;
|
|
198
|
-
/** Hard delete all records in this collection */
|
|
199
|
-
deleteAll(): Promise<number>;
|
|
200
|
-
/** Soft delete a record by ID (sets deleted_at) */
|
|
201
|
-
softDelete(id: string): Promise<boolean>;
|
|
202
|
-
/** Restore a soft-deleted record */
|
|
203
|
-
restore(id: string): Promise<boolean>;
|
|
204
203
|
/** Search records by JSONB contains */
|
|
205
204
|
search(criteria: Partial<T>): Promise<DataRecord<T>[]>;
|
|
206
205
|
/** Count records in collection */
|
|
@@ -246,39 +245,59 @@ interface DataServiceClient {
|
|
|
246
245
|
* 4. Secure: RLS enforced, token-based auth
|
|
247
246
|
*/
|
|
248
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Set debug token for testing/debugging
|
|
250
|
+
* This allows you to bypass the token fetch logic during development
|
|
251
|
+
*
|
|
252
|
+
* @param token The token to use for debugging
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* import { debugSetToken, createClient } from '@seaverse/dataservice';
|
|
257
|
+
*
|
|
258
|
+
* // Set debug token before creating client
|
|
259
|
+
* debugSetToken('your-test-token');
|
|
260
|
+
*
|
|
261
|
+
* // Client will use debug token instead of fetching
|
|
262
|
+
* const client = await createClient({});
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
declare function debugSetToken(token: string): void;
|
|
249
266
|
/**
|
|
250
267
|
* Create a new Data Service client
|
|
251
268
|
*
|
|
252
269
|
* AI-friendly: Single entry point with clear configuration
|
|
253
270
|
*
|
|
254
|
-
* @param config - Client configuration
|
|
271
|
+
* @param config - Client configuration (token optional, will auto-fetch from parent if in iframe)
|
|
255
272
|
* @returns DataServiceClient instance
|
|
256
273
|
*
|
|
257
274
|
* @example
|
|
258
275
|
* ```typescript
|
|
259
|
-
* //
|
|
260
|
-
* const client = createClient({
|
|
276
|
+
* // Auto-fetch token from parent page (iframe only)
|
|
277
|
+
* const client = await createClient({});
|
|
278
|
+
*
|
|
279
|
+
* // Or provide token explicitly
|
|
280
|
+
* const client = await createClient({
|
|
261
281
|
* token: 'your-jwt-token-here',
|
|
262
282
|
* });
|
|
263
283
|
*
|
|
264
|
-
* //
|
|
265
|
-
* const client = createClient({
|
|
284
|
+
* // Bearer prefix is auto-added
|
|
285
|
+
* const client = await createClient({
|
|
266
286
|
* token: 'Bearer your-jwt-token-here',
|
|
267
287
|
* });
|
|
268
288
|
*
|
|
269
|
-
* //
|
|
270
|
-
* const client = createClient({
|
|
289
|
+
* // Custom endpoint
|
|
290
|
+
* const client = await createClient({
|
|
271
291
|
* url: 'https://your-postgrest-api.example.com',
|
|
272
292
|
* token: 'your-jwt-token-here',
|
|
273
293
|
* });
|
|
274
294
|
*
|
|
275
295
|
* // appId is automatically extracted from current URL
|
|
276
|
-
* // Direct access to collections - clean and simple
|
|
277
296
|
* const order = await client.userData.collection('orders').insert({ ... });
|
|
278
297
|
* const orders = await client.userData.collection('orders').select().execute();
|
|
279
298
|
* ```
|
|
280
299
|
*/
|
|
281
|
-
declare function createClient(config
|
|
300
|
+
declare function createClient(config?: ClientConfig): Promise<DataServiceClient>;
|
|
282
301
|
|
|
283
302
|
/**
|
|
284
303
|
* SeaVerse Data Service SDK
|
|
@@ -287,18 +306,21 @@ declare function createClient(config: ClientConfig): DataServiceClient;
|
|
|
287
306
|
*
|
|
288
307
|
* @packageDocumentation
|
|
289
308
|
*
|
|
290
|
-
* @example Basic Usage
|
|
309
|
+
* @example Basic Usage with Auto Token Fetch
|
|
291
310
|
* ```typescript
|
|
292
311
|
* import { createClient } from '@seaverse/dataservice';
|
|
293
312
|
*
|
|
294
|
-
* //
|
|
295
|
-
* const client = createClient({
|
|
313
|
+
* // Auto-fetch token from parent page (iframe only)
|
|
314
|
+
* const client = await createClient({});
|
|
315
|
+
*
|
|
316
|
+
* // Or provide token explicitly
|
|
317
|
+
* const client = await createClient({
|
|
296
318
|
* token: 'your-jwt-token',
|
|
297
319
|
* });
|
|
298
320
|
*
|
|
299
321
|
* // appId is automatically extracted from current URL
|
|
300
322
|
* // Insert data
|
|
301
|
-
* const order = await client.userData.collection('
|
|
323
|
+
* const order = await client.userData.collection('order_001').insert({
|
|
302
324
|
* order_number: 'ORD-123',
|
|
303
325
|
* status: 'pending',
|
|
304
326
|
* total: 99.99,
|
|
@@ -306,7 +328,7 @@ declare function createClient(config: ClientConfig): DataServiceClient;
|
|
|
306
328
|
*
|
|
307
329
|
* // Query data
|
|
308
330
|
* const orders = await client.userData
|
|
309
|
-
* .collection('
|
|
331
|
+
* .collection('order_001')
|
|
310
332
|
* .select()
|
|
311
333
|
* .eq('data->>status', 'pending')
|
|
312
334
|
* .order('created_at', { descending: true })
|
|
@@ -317,4 +339,4 @@ declare function createClient(config: ClientConfig): DataServiceClient;
|
|
|
317
339
|
|
|
318
340
|
declare const VERSION = "1.0.0";
|
|
319
341
|
|
|
320
|
-
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient };
|
|
342
|
+
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
interface ClientConfig {
|
|
20
20
|
/** PostgREST API base URL (default: https://dataservice-api.seaverse.ai) */
|
|
21
21
|
url?: string;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* JWT token containing user_id in payload.user_id
|
|
24
|
+
* If not provided, will attempt to fetch from parent page via PostMessage (iframe only)
|
|
25
|
+
*/
|
|
26
|
+
token?: string;
|
|
24
27
|
/** Optional configuration */
|
|
25
28
|
options?: {
|
|
26
29
|
/** Custom fetch implementation (useful for Node.js < 18) */
|
|
@@ -29,6 +32,8 @@ interface ClientConfig {
|
|
|
29
32
|
headers?: Record<string, string>;
|
|
30
33
|
/** Request timeout in milliseconds (default: 30000) */
|
|
31
34
|
timeout?: number;
|
|
35
|
+
/** Timeout for fetching token from parent (milliseconds, default: 5000) */
|
|
36
|
+
tokenFetchTimeout?: number;
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
@@ -193,14 +198,8 @@ interface Collection<T = any> {
|
|
|
193
198
|
update(id: string, data: T): Promise<DataRecord<T>>;
|
|
194
199
|
/** Patch a record by ID (merges with existing data) */
|
|
195
200
|
patch(id: string, partial: Partial<T>): Promise<DataRecord<T>>;
|
|
196
|
-
/**
|
|
201
|
+
/** Delete a record by ID (permanent) */
|
|
197
202
|
delete(id: string): Promise<void>;
|
|
198
|
-
/** Hard delete all records in this collection */
|
|
199
|
-
deleteAll(): Promise<number>;
|
|
200
|
-
/** Soft delete a record by ID (sets deleted_at) */
|
|
201
|
-
softDelete(id: string): Promise<boolean>;
|
|
202
|
-
/** Restore a soft-deleted record */
|
|
203
|
-
restore(id: string): Promise<boolean>;
|
|
204
203
|
/** Search records by JSONB contains */
|
|
205
204
|
search(criteria: Partial<T>): Promise<DataRecord<T>[]>;
|
|
206
205
|
/** Count records in collection */
|
|
@@ -246,39 +245,59 @@ interface DataServiceClient {
|
|
|
246
245
|
* 4. Secure: RLS enforced, token-based auth
|
|
247
246
|
*/
|
|
248
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Set debug token for testing/debugging
|
|
250
|
+
* This allows you to bypass the token fetch logic during development
|
|
251
|
+
*
|
|
252
|
+
* @param token The token to use for debugging
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* import { debugSetToken, createClient } from '@seaverse/dataservice';
|
|
257
|
+
*
|
|
258
|
+
* // Set debug token before creating client
|
|
259
|
+
* debugSetToken('your-test-token');
|
|
260
|
+
*
|
|
261
|
+
* // Client will use debug token instead of fetching
|
|
262
|
+
* const client = await createClient({});
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
declare function debugSetToken(token: string): void;
|
|
249
266
|
/**
|
|
250
267
|
* Create a new Data Service client
|
|
251
268
|
*
|
|
252
269
|
* AI-friendly: Single entry point with clear configuration
|
|
253
270
|
*
|
|
254
|
-
* @param config - Client configuration
|
|
271
|
+
* @param config - Client configuration (token optional, will auto-fetch from parent if in iframe)
|
|
255
272
|
* @returns DataServiceClient instance
|
|
256
273
|
*
|
|
257
274
|
* @example
|
|
258
275
|
* ```typescript
|
|
259
|
-
* //
|
|
260
|
-
* const client = createClient({
|
|
276
|
+
* // Auto-fetch token from parent page (iframe only)
|
|
277
|
+
* const client = await createClient({});
|
|
278
|
+
*
|
|
279
|
+
* // Or provide token explicitly
|
|
280
|
+
* const client = await createClient({
|
|
261
281
|
* token: 'your-jwt-token-here',
|
|
262
282
|
* });
|
|
263
283
|
*
|
|
264
|
-
* //
|
|
265
|
-
* const client = createClient({
|
|
284
|
+
* // Bearer prefix is auto-added
|
|
285
|
+
* const client = await createClient({
|
|
266
286
|
* token: 'Bearer your-jwt-token-here',
|
|
267
287
|
* });
|
|
268
288
|
*
|
|
269
|
-
* //
|
|
270
|
-
* const client = createClient({
|
|
289
|
+
* // Custom endpoint
|
|
290
|
+
* const client = await createClient({
|
|
271
291
|
* url: 'https://your-postgrest-api.example.com',
|
|
272
292
|
* token: 'your-jwt-token-here',
|
|
273
293
|
* });
|
|
274
294
|
*
|
|
275
295
|
* // appId is automatically extracted from current URL
|
|
276
|
-
* // Direct access to collections - clean and simple
|
|
277
296
|
* const order = await client.userData.collection('orders').insert({ ... });
|
|
278
297
|
* const orders = await client.userData.collection('orders').select().execute();
|
|
279
298
|
* ```
|
|
280
299
|
*/
|
|
281
|
-
declare function createClient(config
|
|
300
|
+
declare function createClient(config?: ClientConfig): Promise<DataServiceClient>;
|
|
282
301
|
|
|
283
302
|
/**
|
|
284
303
|
* SeaVerse Data Service SDK
|
|
@@ -287,18 +306,21 @@ declare function createClient(config: ClientConfig): DataServiceClient;
|
|
|
287
306
|
*
|
|
288
307
|
* @packageDocumentation
|
|
289
308
|
*
|
|
290
|
-
* @example Basic Usage
|
|
309
|
+
* @example Basic Usage with Auto Token Fetch
|
|
291
310
|
* ```typescript
|
|
292
311
|
* import { createClient } from '@seaverse/dataservice';
|
|
293
312
|
*
|
|
294
|
-
* //
|
|
295
|
-
* const client = createClient({
|
|
313
|
+
* // Auto-fetch token from parent page (iframe only)
|
|
314
|
+
* const client = await createClient({});
|
|
315
|
+
*
|
|
316
|
+
* // Or provide token explicitly
|
|
317
|
+
* const client = await createClient({
|
|
296
318
|
* token: 'your-jwt-token',
|
|
297
319
|
* });
|
|
298
320
|
*
|
|
299
321
|
* // appId is automatically extracted from current URL
|
|
300
322
|
* // Insert data
|
|
301
|
-
* const order = await client.userData.collection('
|
|
323
|
+
* const order = await client.userData.collection('order_001').insert({
|
|
302
324
|
* order_number: 'ORD-123',
|
|
303
325
|
* status: 'pending',
|
|
304
326
|
* total: 99.99,
|
|
@@ -306,7 +328,7 @@ declare function createClient(config: ClientConfig): DataServiceClient;
|
|
|
306
328
|
*
|
|
307
329
|
* // Query data
|
|
308
330
|
* const orders = await client.userData
|
|
309
|
-
* .collection('
|
|
331
|
+
* .collection('order_001')
|
|
310
332
|
* .select()
|
|
311
333
|
* .eq('data->>status', 'pending')
|
|
312
334
|
* .order('created_at', { descending: true })
|
|
@@ -317,4 +339,4 @@ declare function createClient(config: ClientConfig): DataServiceClient;
|
|
|
317
339
|
|
|
318
340
|
declare const VERSION = "1.0.0";
|
|
319
341
|
|
|
320
|
-
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient };
|
|
342
|
+
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken };
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,8 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
DataServiceError: () => DataServiceError,
|
|
24
24
|
VERSION: () => VERSION,
|
|
25
|
-
createClient: () => createClient
|
|
25
|
+
createClient: () => createClient,
|
|
26
|
+
debugSetToken: () => debugSetToken
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(src_exports);
|
|
28
29
|
|
|
@@ -39,6 +40,54 @@ var DataServiceError = class extends Error {
|
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
// src/client.ts
|
|
43
|
+
var debugToken = null;
|
|
44
|
+
function debugSetToken(token) {
|
|
45
|
+
debugToken = token;
|
|
46
|
+
}
|
|
47
|
+
function isInIframe() {
|
|
48
|
+
try {
|
|
49
|
+
return typeof globalThis !== "undefined" && "window" in globalThis && globalThis.window.self !== globalThis.window.top;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function getTokenFromParent(timeout = 5e3) {
|
|
55
|
+
if (!isInIframe()) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const messageHandler = (event) => {
|
|
60
|
+
if (event.data && event.data.type === "seaverse:token") {
|
|
61
|
+
cleanup();
|
|
62
|
+
const token = event.data.payload?.accessToken;
|
|
63
|
+
resolve(token || null);
|
|
64
|
+
} else if (event.data && event.data.type === "seaverse:error") {
|
|
65
|
+
cleanup();
|
|
66
|
+
console.warn("[SeaVerse DataService SDK] Error getting token from parent:", event.data.error);
|
|
67
|
+
resolve(null);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const timeoutId = setTimeout(() => {
|
|
71
|
+
cleanup();
|
|
72
|
+
resolve(null);
|
|
73
|
+
}, timeout);
|
|
74
|
+
const cleanup = () => {
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
globalThis.window.removeEventListener("message", messageHandler);
|
|
77
|
+
};
|
|
78
|
+
globalThis.window.addEventListener("message", messageHandler);
|
|
79
|
+
try {
|
|
80
|
+
globalThis.window.parent.postMessage(
|
|
81
|
+
{ type: "seaverse:get_token" },
|
|
82
|
+
"*"
|
|
83
|
+
// Allow any origin, supports cross-domain scenarios
|
|
84
|
+
);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
cleanup();
|
|
87
|
+
resolve(null);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
42
91
|
function extractAppId() {
|
|
43
92
|
if (typeof globalThis !== "undefined" && "location" in globalThis) {
|
|
44
93
|
const location = globalThis.location;
|
|
@@ -56,17 +105,27 @@ var HTTPClient = class {
|
|
|
56
105
|
headers;
|
|
57
106
|
fetchFn;
|
|
58
107
|
timeout;
|
|
59
|
-
|
|
108
|
+
tokenPromise = null;
|
|
109
|
+
constructor(config, token) {
|
|
60
110
|
this.baseUrl = (config.url || "https://dataservice-api.seaverse.ai").replace(/\/$/, "");
|
|
61
111
|
this.fetchFn = config.options?.fetch || globalThis.fetch;
|
|
62
112
|
this.timeout = config.options?.timeout || 3e4;
|
|
63
|
-
const token = config.token.startsWith("Bearer ") ? config.token : `Bearer ${config.token}`;
|
|
64
113
|
this.headers = {
|
|
65
|
-
"Authorization": token,
|
|
66
114
|
"Content-Type": "application/json",
|
|
67
115
|
"Prefer": "return=representation",
|
|
68
116
|
...config.options?.headers
|
|
69
117
|
};
|
|
118
|
+
if (token) {
|
|
119
|
+
const authToken = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
|
|
120
|
+
this.headers["Authorization"] = authToken;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Set authentication token (called after async token fetch)
|
|
125
|
+
*/
|
|
126
|
+
setToken(token) {
|
|
127
|
+
const authToken = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
|
|
128
|
+
this.headers["Authorization"] = authToken;
|
|
70
129
|
}
|
|
71
130
|
/**
|
|
72
131
|
* Make HTTP request with timeout and error handling
|
|
@@ -286,30 +345,6 @@ var CollectionImpl = class {
|
|
|
286
345
|
`/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
|
|
287
346
|
);
|
|
288
347
|
}
|
|
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
|
-
}
|
|
301
|
-
async softDelete(id) {
|
|
302
|
-
const response = await this.client.post("/rpc/soft_delete_user_data", {
|
|
303
|
-
p_id: id
|
|
304
|
-
});
|
|
305
|
-
return response;
|
|
306
|
-
}
|
|
307
|
-
async restore(id) {
|
|
308
|
-
const response = await this.client.post("/rpc/restore_user_data", {
|
|
309
|
-
p_id: id
|
|
310
|
-
});
|
|
311
|
-
return response;
|
|
312
|
-
}
|
|
313
348
|
async search(criteria) {
|
|
314
349
|
return this.client.get(this.tablePath, {
|
|
315
350
|
app_id: `eq.${this.appId}`,
|
|
@@ -357,9 +392,9 @@ var DataServiceClientImpl = class {
|
|
|
357
392
|
client;
|
|
358
393
|
userData;
|
|
359
394
|
appId;
|
|
360
|
-
constructor(
|
|
361
|
-
this.client =
|
|
362
|
-
this.appId =
|
|
395
|
+
constructor(client, appId) {
|
|
396
|
+
this.client = client;
|
|
397
|
+
this.appId = appId;
|
|
363
398
|
this.userData = new DataTableImpl(this.client, "/user_data", this.appId);
|
|
364
399
|
}
|
|
365
400
|
async getStats() {
|
|
@@ -370,8 +405,26 @@ var DataServiceClientImpl = class {
|
|
|
370
405
|
return this.client.post("/rpc/health");
|
|
371
406
|
}
|
|
372
407
|
};
|
|
373
|
-
function createClient(config) {
|
|
374
|
-
|
|
408
|
+
async function createClient(config = {}) {
|
|
409
|
+
let token = config.token;
|
|
410
|
+
if (!token) {
|
|
411
|
+
if (debugToken) {
|
|
412
|
+
token = debugToken;
|
|
413
|
+
} else {
|
|
414
|
+
const timeout = config.options?.tokenFetchTimeout || 5e3;
|
|
415
|
+
const fetchedToken = await getTokenFromParent(timeout);
|
|
416
|
+
if (!fetchedToken) {
|
|
417
|
+
throw new DataServiceError(
|
|
418
|
+
"No token provided and failed to fetch from parent page. Please provide a token or ensure the app is running in an iframe with a parent that responds to seaverse:get_token messages.",
|
|
419
|
+
"NO_TOKEN"
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
token = fetchedToken;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const httpClient = new HTTPClient(config, token);
|
|
426
|
+
const appId = extractAppId();
|
|
427
|
+
return new DataServiceClientImpl(httpClient, appId);
|
|
375
428
|
}
|
|
376
429
|
|
|
377
430
|
// src/index.ts
|
|
@@ -380,5 +433,6 @@ var VERSION = "1.0.0";
|
|
|
380
433
|
0 && (module.exports = {
|
|
381
434
|
DataServiceError,
|
|
382
435
|
VERSION,
|
|
383
|
-
createClient
|
|
436
|
+
createClient,
|
|
437
|
+
debugSetToken
|
|
384
438
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,54 @@ var DataServiceError = class extends Error {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// src/client.ts
|
|
14
|
+
var debugToken = null;
|
|
15
|
+
function debugSetToken(token) {
|
|
16
|
+
debugToken = token;
|
|
17
|
+
}
|
|
18
|
+
function isInIframe() {
|
|
19
|
+
try {
|
|
20
|
+
return typeof globalThis !== "undefined" && "window" in globalThis && globalThis.window.self !== globalThis.window.top;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function getTokenFromParent(timeout = 5e3) {
|
|
26
|
+
if (!isInIframe()) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const messageHandler = (event) => {
|
|
31
|
+
if (event.data && event.data.type === "seaverse:token") {
|
|
32
|
+
cleanup();
|
|
33
|
+
const token = event.data.payload?.accessToken;
|
|
34
|
+
resolve(token || null);
|
|
35
|
+
} else if (event.data && event.data.type === "seaverse:error") {
|
|
36
|
+
cleanup();
|
|
37
|
+
console.warn("[SeaVerse DataService SDK] Error getting token from parent:", event.data.error);
|
|
38
|
+
resolve(null);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const timeoutId = setTimeout(() => {
|
|
42
|
+
cleanup();
|
|
43
|
+
resolve(null);
|
|
44
|
+
}, timeout);
|
|
45
|
+
const cleanup = () => {
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
globalThis.window.removeEventListener("message", messageHandler);
|
|
48
|
+
};
|
|
49
|
+
globalThis.window.addEventListener("message", messageHandler);
|
|
50
|
+
try {
|
|
51
|
+
globalThis.window.parent.postMessage(
|
|
52
|
+
{ type: "seaverse:get_token" },
|
|
53
|
+
"*"
|
|
54
|
+
// Allow any origin, supports cross-domain scenarios
|
|
55
|
+
);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
cleanup();
|
|
58
|
+
resolve(null);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
14
62
|
function extractAppId() {
|
|
15
63
|
if (typeof globalThis !== "undefined" && "location" in globalThis) {
|
|
16
64
|
const location = globalThis.location;
|
|
@@ -28,17 +76,27 @@ var HTTPClient = class {
|
|
|
28
76
|
headers;
|
|
29
77
|
fetchFn;
|
|
30
78
|
timeout;
|
|
31
|
-
|
|
79
|
+
tokenPromise = null;
|
|
80
|
+
constructor(config, token) {
|
|
32
81
|
this.baseUrl = (config.url || "https://dataservice-api.seaverse.ai").replace(/\/$/, "");
|
|
33
82
|
this.fetchFn = config.options?.fetch || globalThis.fetch;
|
|
34
83
|
this.timeout = config.options?.timeout || 3e4;
|
|
35
|
-
const token = config.token.startsWith("Bearer ") ? config.token : `Bearer ${config.token}`;
|
|
36
84
|
this.headers = {
|
|
37
|
-
"Authorization": token,
|
|
38
85
|
"Content-Type": "application/json",
|
|
39
86
|
"Prefer": "return=representation",
|
|
40
87
|
...config.options?.headers
|
|
41
88
|
};
|
|
89
|
+
if (token) {
|
|
90
|
+
const authToken = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
|
|
91
|
+
this.headers["Authorization"] = authToken;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Set authentication token (called after async token fetch)
|
|
96
|
+
*/
|
|
97
|
+
setToken(token) {
|
|
98
|
+
const authToken = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
|
|
99
|
+
this.headers["Authorization"] = authToken;
|
|
42
100
|
}
|
|
43
101
|
/**
|
|
44
102
|
* Make HTTP request with timeout and error handling
|
|
@@ -258,30 +316,6 @@ var CollectionImpl = class {
|
|
|
258
316
|
`/user_data?id=eq.${id}&app_id=eq.${this.appId}&collection_name=eq.${this.collectionName}`
|
|
259
317
|
);
|
|
260
318
|
}
|
|
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
|
-
}
|
|
273
|
-
async softDelete(id) {
|
|
274
|
-
const response = await this.client.post("/rpc/soft_delete_user_data", {
|
|
275
|
-
p_id: id
|
|
276
|
-
});
|
|
277
|
-
return response;
|
|
278
|
-
}
|
|
279
|
-
async restore(id) {
|
|
280
|
-
const response = await this.client.post("/rpc/restore_user_data", {
|
|
281
|
-
p_id: id
|
|
282
|
-
});
|
|
283
|
-
return response;
|
|
284
|
-
}
|
|
285
319
|
async search(criteria) {
|
|
286
320
|
return this.client.get(this.tablePath, {
|
|
287
321
|
app_id: `eq.${this.appId}`,
|
|
@@ -329,9 +363,9 @@ var DataServiceClientImpl = class {
|
|
|
329
363
|
client;
|
|
330
364
|
userData;
|
|
331
365
|
appId;
|
|
332
|
-
constructor(
|
|
333
|
-
this.client =
|
|
334
|
-
this.appId =
|
|
366
|
+
constructor(client, appId) {
|
|
367
|
+
this.client = client;
|
|
368
|
+
this.appId = appId;
|
|
335
369
|
this.userData = new DataTableImpl(this.client, "/user_data", this.appId);
|
|
336
370
|
}
|
|
337
371
|
async getStats() {
|
|
@@ -342,8 +376,26 @@ var DataServiceClientImpl = class {
|
|
|
342
376
|
return this.client.post("/rpc/health");
|
|
343
377
|
}
|
|
344
378
|
};
|
|
345
|
-
function createClient(config) {
|
|
346
|
-
|
|
379
|
+
async function createClient(config = {}) {
|
|
380
|
+
let token = config.token;
|
|
381
|
+
if (!token) {
|
|
382
|
+
if (debugToken) {
|
|
383
|
+
token = debugToken;
|
|
384
|
+
} else {
|
|
385
|
+
const timeout = config.options?.tokenFetchTimeout || 5e3;
|
|
386
|
+
const fetchedToken = await getTokenFromParent(timeout);
|
|
387
|
+
if (!fetchedToken) {
|
|
388
|
+
throw new DataServiceError(
|
|
389
|
+
"No token provided and failed to fetch from parent page. Please provide a token or ensure the app is running in an iframe with a parent that responds to seaverse:get_token messages.",
|
|
390
|
+
"NO_TOKEN"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
token = fetchedToken;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const httpClient = new HTTPClient(config, token);
|
|
397
|
+
const appId = extractAppId();
|
|
398
|
+
return new DataServiceClientImpl(httpClient, appId);
|
|
347
399
|
}
|
|
348
400
|
|
|
349
401
|
// src/index.ts
|
|
@@ -351,5 +403,6 @@ var VERSION = "1.0.0";
|
|
|
351
403
|
export {
|
|
352
404
|
DataServiceError,
|
|
353
405
|
VERSION,
|
|
354
|
-
createClient
|
|
406
|
+
createClient,
|
|
407
|
+
debugSetToken
|
|
355
408
|
};
|