@seaverse/data-service-sdk 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  SeaVerse Data Service SDK for accessing Firestore with secure token management and three-tier permission model.
4
4
 
5
+ ## 🤖 For LLM Users
6
+
7
+ **👉 Want the simplest way to use this SDK? Check out the [LLM Quick Start Guide](./LLM-QUICK-START.md)**
8
+
9
+ The Quick Start guide shows you the **recommended LLM-friendly API** that automatically handles:
10
+ - ✅ Required fields (`_appId`, `_createdAt`, `_createdBy`)
11
+ - ✅ Correct Firestore paths
12
+ - ✅ Server timestamps
13
+ - ✅ Single import source
14
+
15
+ **Most common LLM mistakes prevented!**
16
+
5
17
  ## Features
6
18
 
7
19
  - 🔐 Secure Firestore token generation for authenticated users and guests
@@ -10,6 +22,7 @@ SeaVerse Data Service SDK for accessing Firestore with secure token management a
10
22
  - 🔒 Automatic data isolation by app_id
11
23
  - 📝 TypeScript support with full type definitions
12
24
  - 🤖 LLM-friendly documentation with clear examples
25
+ - 🛡️ Path helper functions to prevent permission-denied errors
13
26
 
14
27
  ## Three-Tier Permission Model
15
28
 
@@ -130,14 +143,112 @@ const { DataServiceClient } = require('@seaverse/data-service-sdk');
130
143
  import { DataServiceClient } from '@seaverse/data-service-sdk';
131
144
  ```
132
145
 
146
+ ## Path Helper Functions (🚨 Recommended for LLM)
147
+
148
+ To prevent `permission-denied` errors caused by incorrect paths, we provide helper functions that generate the correct Firestore paths automatically.
149
+
150
+ ### Why Use Path Helpers?
151
+
152
+ **Problem**: LLM or developers might accidentally use wrong paths:
153
+ ```typescript
154
+ // ❌ WRONG - Will cause permission-denied!
155
+ collection(db, `apps/${appId}/publicArticles`) // Not matching security rules!
156
+ collection(db, `apps/${appId}/users/${userId}/articles`) // Not matching security rules!
157
+ ```
158
+
159
+ **Solution**: Use path helper functions:
160
+ ```typescript
161
+ import { getPublicDataPath, getUserDataPath } from '@seaverse/data-service-sdk';
162
+
163
+ // ✅ CORRECT - Guaranteed to match security rules
164
+ collection(db, getPublicDataPath(appId, 'posts')) // → appData/{appId}/publicData/posts
165
+ collection(db, getUserDataPath(appId, userId, 'notes')) // → appData/{appId}/userData/{userId}/notes
166
+ ```
167
+
168
+ ### Available Path Helpers
169
+
170
+ ```typescript
171
+ import {
172
+ getPublicReadPath, // For read-only public data
173
+ getPublicDataPath, // For public read/write data
174
+ getUserDataPath, // For private user data
175
+ getPublicReadDocPath, // For specific public document
176
+ getPublicDataDocPath, // For specific public data document
177
+ getUserDataDocPath, // For specific user document
178
+ PathBuilder // For advanced path building
179
+ } from '@seaverse/data-service-sdk';
180
+ ```
181
+
182
+ ### Basic Usage Examples
183
+
184
+ ```typescript
185
+ // Public data that everyone can read/write
186
+ const postsPath = getPublicDataPath(appId, 'posts');
187
+ await addDoc(collection(db, postsPath), {
188
+ _appId: appId,
189
+ _createdAt: serverTimestamp(),
190
+ _createdBy: userId,
191
+ title: 'My Post'
192
+ });
193
+
194
+ // Private user data
195
+ const notesPath = getUserDataPath(appId, userId, 'notes');
196
+ await addDoc(collection(db, notesPath), {
197
+ _appId: appId,
198
+ _createdAt: serverTimestamp(),
199
+ _createdBy: userId,
200
+ content: 'Private note'
201
+ });
202
+
203
+ // Public read-only data (admin writes only)
204
+ const configPath = getPublicReadPath(appId, 'config');
205
+ const configs = await getDocs(collection(db, configPath));
206
+
207
+ // Access specific document
208
+ const docPath = getPublicDataDocPath(appId, 'posts', 'post-123');
209
+ const docSnap = await getDoc(doc(db, docPath));
210
+ ```
211
+
212
+ ### Advanced: PathBuilder
213
+
214
+ For complex path construction:
215
+
216
+ ```typescript
217
+ import { PathBuilder } from '@seaverse/data-service-sdk';
218
+
219
+ const builder = new PathBuilder(appId);
220
+
221
+ // Build collection path
222
+ const path = builder.publicData('posts').build();
223
+ // Returns: 'appData/my-app/publicData/posts'
224
+
225
+ // Build document path
226
+ const docPath = builder.publicData('posts').doc('post-123').build();
227
+ // Returns: 'appData/my-app/publicData/posts/post-123'
228
+
229
+ // Build user data path
230
+ const userPath = builder.userData(userId, 'notes').build();
231
+ // Returns: 'appData/my-app/userData/user-123/notes'
232
+ ```
233
+
234
+ ### Error Prevention
235
+
236
+ Path helpers validate inputs to prevent common mistakes:
237
+
238
+ ```typescript
239
+ // ❌ These will throw errors:
240
+ getPublicDataPath('my-app', 'posts/comments'); // Error: cannot contain /
241
+ getPublicDataPath('my-app', ''); // Error: must be non-empty string
242
+ getUserDataPath('my-app', '', 'notes'); // Error: userId must be non-empty
243
+ ```
244
+
133
245
  ## Quick Start
134
246
 
135
- ### 🚀 Easiest Way (Recommended - Auto Firebase Setup)
247
+ ### 🚀 Easiest Way (🤖 LLM-Recommended)
136
248
 
137
249
  ```typescript
138
250
  import { DataServiceClient, initializeWithToken } from '@seaverse/data-service-sdk';
139
251
  import { AuthClient } from '@seaverse/auth-sdk';
140
- import { collection, addDoc, getDocs, serverTimestamp } from 'firebase/firestore';
141
252
 
142
253
  // Step 1: Login user
143
254
  const authClient = new AuthClient({ appId: 'my-app-123' });
@@ -153,41 +264,65 @@ const tokenResponse = await dataClient.generateFirestoreToken({
153
264
  app_id: 'my-app-123'
154
265
  });
155
266
 
156
- // Step 3: Auto-initialize Firebase (ONE LINE!)
157
- const { db, appId, userId } = await initializeWithToken(tokenResponse);
267
+ // Step 3: Initialize Firebase & get helper
268
+ const { helper } = await initializeWithToken(tokenResponse);
158
269
 
159
- // Step 4: Use Firestore directly!
270
+ // Step 4: Use Firestore with helper (automatic required fields!)
160
271
 
161
- // Write to publicData (everyone can write)
162
- await addDoc(collection(db, `appData/${appId}/publicData/posts`), {
163
- _appId: appId, // REQUIRED
164
- _createdAt: serverTimestamp(), // REQUIRED
165
- _createdBy: userId, // REQUIRED
272
+ // ✅ LLM-FRIENDLY: Write to publicData - required fields auto-injected!
273
+ await helper.addToPublicData('posts', {
166
274
  title: 'My First Post',
167
275
  content: 'Hello world!'
168
276
  });
169
277
 
170
- // Read from publicData
171
- const snapshot = await getDocs(collection(db, `appData/${appId}/publicData/posts`));
172
- snapshot.forEach(doc => {
278
+ // ✅ LLM-FRIENDLY: Read from publicData
279
+ const posts = await helper.getPublicData('posts');
280
+ posts.forEach(doc => {
173
281
  console.log(doc.id, doc.data());
174
282
  });
175
283
 
176
- // Write to userData (private)
177
- await addDoc(collection(db, `appData/${appId}/userData/${userId}/notes`), {
284
+ // ✅ LLM-FRIENDLY: Write to userData (private) - auto-isolated!
285
+ await helper.addToUserData('notes', {
286
+ title: 'Private Note',
287
+ content: 'Only I can see this'
288
+ });
289
+ ```
290
+
291
+ <details>
292
+ <summary>📖 Advanced: Manual Way (for fine-grained control)</summary>
293
+
294
+ ```typescript
295
+ import {
296
+ DataServiceClient,
297
+ initializeWithToken,
298
+ getPublicDataPath,
299
+ getUserDataPath,
300
+ collection,
301
+ addDoc,
302
+ getDocs,
303
+ serverTimestamp
304
+ } from '@seaverse/data-service-sdk';
305
+ import { AuthClient } from '@seaverse/auth-sdk';
306
+
307
+ // ... login and initialize ...
308
+ const { db, appId, userId } = await initializeWithToken(tokenResponse);
309
+
310
+ // Manual way: Use path helpers + required fields
311
+ const postsPath = getPublicDataPath(appId, 'posts');
312
+ await addDoc(collection(db, postsPath), {
178
313
  _appId: appId, // REQUIRED
179
314
  _createdAt: serverTimestamp(), // REQUIRED
180
315
  _createdBy: userId, // REQUIRED
181
- title: 'Private Note',
182
- content: 'Only I can see this'
316
+ title: 'My First Post',
317
+ content: 'Hello world!'
183
318
  });
184
319
  ```
320
+ </details>
185
321
 
186
- ### 👤 For Guest Users (Even Simpler!)
322
+ ### 👤 For Guest Users (🤖 Even Simpler!)
187
323
 
188
324
  ```typescript
189
325
  import { DataServiceClient, initializeWithToken } from '@seaverse/data-service-sdk';
190
- import { collection, addDoc, serverTimestamp } from 'firebase/firestore';
191
326
 
192
327
  // Step 1: Get guest token (no authentication needed!)
193
328
  const dataClient = new DataServiceClient();
@@ -195,14 +330,11 @@ const tokenResponse = await dataClient.generateGuestFirestoreToken({
195
330
  app_id: 'my-app-123'
196
331
  });
197
332
 
198
- // Step 2: Auto-initialize Firebase (ONE LINE!)
199
- const { db, appId, userId } = await initializeWithToken(tokenResponse);
333
+ // Step 2: Initialize & get helper
334
+ const { helper } = await initializeWithToken(tokenResponse);
200
335
 
201
- // Step 3: Guest can write to publicData
202
- await addDoc(collection(db, `appData/${appId}/publicData/comments`), {
203
- _appId: appId,
204
- _createdAt: serverTimestamp(),
205
- _createdBy: userId, // Guest user ID (e.g., 'guest-abc123')
336
+ // Step 3: Guest can write to publicData (automatic required fields!)
337
+ await helper.addToPublicData('comments', {
206
338
  comment: 'Great app!',
207
339
  rating: 5
208
340
  });
@@ -566,7 +698,19 @@ import type {
566
698
 
567
699
  When using this SDK with LLM-generated code:
568
700
 
569
- 1. **Always include required fields:**
701
+ 1. **🛡️ MOST IMPORTANT: Use path helper functions to avoid permission-denied errors:**
702
+ ```typescript
703
+ import { getPublicDataPath, getUserDataPath } from '@seaverse/data-service-sdk';
704
+
705
+ // ✅ CORRECT - Use helpers
706
+ const path = getPublicDataPath(appId, 'posts');
707
+ await addDoc(collection(db, path), { ... });
708
+
709
+ // ❌ WRONG - Manual paths may be incorrect
710
+ await addDoc(collection(db, `apps/${appId}/posts`), { ... });
711
+ ```
712
+
713
+ 2. **Always include required fields:**
570
714
  ```typescript
571
715
  {
572
716
  _appId: appId, // From token response
@@ -575,16 +719,26 @@ When using this SDK with LLM-generated code:
575
719
  }
576
720
  ```
577
721
 
578
- 2. **Use correct data paths:**
579
- - publicRead: `appData/${appId}/publicRead/{collection}/{docId}`
580
- - publicData: `appData/${appId}/publicData/{collection}/{docId}`
581
- - userData: `appData/${appId}/userData/${userId}/{collection}/{docId}`
722
+ 3. **Use correct data paths with helpers:**
723
+ ```typescript
724
+ // Use path helpers instead of manual strings!
725
+ import { getPublicReadPath, getPublicDataPath, getUserDataPath } from '@seaverse/data-service-sdk';
726
+
727
+ // publicRead
728
+ const configPath = getPublicReadPath(appId, 'config');
729
+
730
+ // publicData
731
+ const postsPath = getPublicDataPath(appId, 'posts');
732
+
733
+ // userData
734
+ const notesPath = getUserDataPath(appId, userId, 'notes');
735
+ ```
582
736
 
583
- 3. **Handle token expiration:**
737
+ 4. **Handle token expiration:**
584
738
  - Tokens expire after 1 hour (3600 seconds)
585
739
  - Check `expires_in` field and refresh when needed
586
740
 
587
- 4. **Use serverTimestamp() for timestamps:**
741
+ 5. **Use serverTimestamp() for timestamps:**
588
742
  ```typescript
589
743
  import { serverTimestamp } from 'firebase/firestore';
590
744
 
@@ -593,7 +747,7 @@ When using this SDK with LLM-generated code:
593
747
  }
594
748
  ```
595
749
 
596
- 5. **Separate guest and authenticated flows:**
750
+ 6. **Separate guest and authenticated flows:**
597
751
  - Use `generateGuestFirestoreToken()` for anonymous users
598
752
  - Use `generateFirestoreToken()` for logged-in users
599
753