@seaverse/data-service-sdk 0.9.0 → 0.10.1
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 +131 -34
- package/dist/browser.js +341 -20
- package/dist/browser.js.map +1 -1
- package/dist/browser.umd.js +348 -19
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +348 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +204 -15
- package/dist/index.js +341 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ SeaVerse Data Service SDK for accessing Firestore with secure token management a
|
|
|
42
42
|
- 📝 TypeScript support with full type definitions
|
|
43
43
|
- 🤖 LLM-friendly documentation with clear examples
|
|
44
44
|
- 🛡️ Path helper functions to prevent permission-denied errors
|
|
45
|
+
- ✅ Built-in validation (reserved fields, document size)
|
|
46
|
+
- 🗑️ Soft delete support (mark as deleted without removing)
|
|
45
47
|
|
|
46
48
|
## Three-Tier Permission Model
|
|
47
49
|
|
|
@@ -51,7 +53,7 @@ SeaVerse organizes your Firestore data into three permission levels:
|
|
|
51
53
|
|-----------------|--------------|-------------|--------------|----------|
|
|
52
54
|
| **publicRead** | `appData/{app_id}/publicRead/_data/{collection}/{docId}` | All authenticated users | Admin only | System configs, announcements, static content |
|
|
53
55
|
| **publicData** | `appData/{app_id}/publicData/_data/{collection}/{docId}` | All authenticated users | All authenticated users | User posts, comments, shared content |
|
|
54
|
-
| **userData** | `appData/{app_id}/userData/{user_id}/{collection}/{docId}` | Owner only | Owner only | User settings, private notes, personal data |
|
|
56
|
+
| **userData** | `appData/{app_id}/userData/{user_id}/_data/{collection}/{docId}` | Owner only | Owner only | User settings, private notes, personal data |
|
|
55
57
|
|
|
56
58
|
### 🚨 CRITICAL: Firestore Path Rules (For LLM)
|
|
57
59
|
|
|
@@ -86,7 +88,7 @@ const postsRef = collection(db, `appData/${appId}/publicData/_data/posts`);
|
|
|
86
88
|
await addDoc(postsRef, { ...data }); // Firestore adds document ID
|
|
87
89
|
|
|
88
90
|
// User Private Data (owner only)
|
|
89
|
-
const notesRef = collection(db, `appData/${appId}/userData/${userId}/notes`);
|
|
91
|
+
const notesRef = collection(db, `appData/${appId}/userData/${userId}/_data/notes`);
|
|
90
92
|
await addDoc(notesRef, { ...data });
|
|
91
93
|
|
|
92
94
|
// Public Read-Only (everyone can read, admin can write)
|
|
@@ -95,9 +97,10 @@ await getDocs(configRef);
|
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
**The pattern is always:**
|
|
98
|
-
- `appData` → `{app_id}` → `{permission_layer}` → `_data` → `{collection}` → (auto-generated doc ID)
|
|
99
|
-
- Collection: 5 segments (odd) ✅
|
|
100
|
-
-
|
|
100
|
+
- `appData` → `{app_id}` → `{permission_layer}` → (`{user_id}` for userData) → `_data` → `{collection}` → (auto-generated doc ID)
|
|
101
|
+
- publicRead/publicData Collection: 5 segments (odd) ✅
|
|
102
|
+
- userData Collection: 6 segments (even) ✅ (includes userId)
|
|
103
|
+
- Document paths: add 1 more segment for docId
|
|
101
104
|
|
|
102
105
|
### Required Fields
|
|
103
106
|
|
|
@@ -113,6 +116,82 @@ All Firestore documents MUST include these three fields:
|
|
|
113
116
|
|
|
114
117
|
These fields are enforced by Firestore Security Rules and ensure proper data isolation.
|
|
115
118
|
|
|
119
|
+
**🎯 Good News for LLM**: When using `FirestoreHelper`, these fields are automatically injected - you don't need to remember them!
|
|
120
|
+
|
|
121
|
+
### Reserved Fields & Validation
|
|
122
|
+
|
|
123
|
+
**⚠️ IMPORTANT: Fields starting with `_` are reserved for system use!**
|
|
124
|
+
|
|
125
|
+
The SDK automatically validates your data to prevent common mistakes:
|
|
126
|
+
|
|
127
|
+
1. **Reserved Fields**: You cannot create custom fields starting with `_`
|
|
128
|
+
```typescript
|
|
129
|
+
// ❌ WRONG - Will throw error
|
|
130
|
+
await helper.addToPublicData('posts', {
|
|
131
|
+
_custom: 'value', // Reserved field!
|
|
132
|
+
title: 'My Post'
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ✅ CORRECT
|
|
136
|
+
await helper.addToPublicData('posts', {
|
|
137
|
+
customField: 'value', // No underscore prefix
|
|
138
|
+
title: 'My Post'
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
2. **Document Size**: Documents are limited to 256 KB
|
|
143
|
+
```typescript
|
|
144
|
+
// SDK will automatically check size and throw error if too large
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
3. **Automatic Validation**: All `FirestoreHelper` methods validate data automatically
|
|
148
|
+
```typescript
|
|
149
|
+
// Manual validation if needed
|
|
150
|
+
import { validateFirestoreData, validateDataDetailed } from '@seaverse/data-service-sdk';
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
validateFirestoreData(myData);
|
|
154
|
+
// Data is valid
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Validation failed:', error.message);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Or get detailed errors without throwing
|
|
160
|
+
const result = validateDataDetailed(myData);
|
|
161
|
+
if (!result.valid) {
|
|
162
|
+
console.error('Errors:', result.errors);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Soft Delete Support
|
|
167
|
+
|
|
168
|
+
**🗑️ Recommended Practice: Use soft delete instead of hard delete**
|
|
169
|
+
|
|
170
|
+
Soft delete marks documents as deleted without removing them from the database:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// ✅ RECOMMENDED: Soft delete (mark as deleted)
|
|
174
|
+
await helper.softDeleteDoc(
|
|
175
|
+
getPublicDataPath(appId, 'posts'),
|
|
176
|
+
'post-123'
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Document is still in database but marked as _deleted = true
|
|
180
|
+
// By default, helper.getPublicData() won't return deleted documents
|
|
181
|
+
|
|
182
|
+
// ❌ Hard delete (only for admins, permanent)
|
|
183
|
+
await helper.deleteDoc(
|
|
184
|
+
getPublicDataPath(appId, 'posts'),
|
|
185
|
+
'post-123'
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Why soft delete?**
|
|
190
|
+
- ✅ Data recovery possible
|
|
191
|
+
- ✅ Audit trail preserved
|
|
192
|
+
- ✅ Safer for production
|
|
193
|
+
- ✅ Follows industry best practices
|
|
194
|
+
|
|
116
195
|
## Installation
|
|
117
196
|
|
|
118
197
|
```bash
|
|
@@ -179,17 +258,17 @@ collection(db, `apps/${appId}/users/${userId}/articles`) // Not matching securi
|
|
|
179
258
|
import { getPublicDataPath, getUserDataPath } from '@seaverse/data-service-sdk';
|
|
180
259
|
|
|
181
260
|
// ✅ CORRECT - Guaranteed to match security rules
|
|
182
|
-
collection(db, getPublicDataPath(appId, 'posts')) // → appData/{appId}/publicData/posts
|
|
183
|
-
collection(db, getUserDataPath(appId, userId, 'notes')) // → appData/{appId}/userData/{userId}/notes
|
|
261
|
+
collection(db, getPublicDataPath(appId, 'posts')) // → appData/{appId}/publicData/_data/posts
|
|
262
|
+
collection(db, getUserDataPath(appId, userId, 'notes')) // → appData/{appId}/userData/{userId}/_data/notes
|
|
184
263
|
```
|
|
185
264
|
|
|
186
265
|
### Available Path Helpers
|
|
187
266
|
|
|
188
267
|
```typescript
|
|
189
268
|
import {
|
|
190
|
-
getPublicReadPath, //
|
|
191
|
-
getPublicDataPath, //
|
|
192
|
-
getUserDataPath, //
|
|
269
|
+
getPublicReadPath, // Returns: appData/{appId}/publicRead/_data/{collection}
|
|
270
|
+
getPublicDataPath, // Returns: appData/{appId}/publicData/_data/{collection}
|
|
271
|
+
getUserDataPath, // Returns: appData/{appId}/userData/{userId}/_data/{collection}
|
|
193
272
|
getPublicReadDocPath, // For specific public document
|
|
194
273
|
getPublicDataDocPath, // For specific public data document
|
|
195
274
|
getUserDataDocPath, // For specific user document
|
|
@@ -246,7 +325,7 @@ const docPath = builder.publicData('posts').doc('post-123').build();
|
|
|
246
325
|
|
|
247
326
|
// Build user data path
|
|
248
327
|
const userPath = builder.userData(userId, 'notes').build();
|
|
249
|
-
// Returns: 'appData/my-app/userData/user-123/notes'
|
|
328
|
+
// Returns: 'appData/my-app/userData/user-123/_data/notes'
|
|
250
329
|
```
|
|
251
330
|
|
|
252
331
|
### Error Prevention
|
|
@@ -600,7 +679,7 @@ const comments = await getDocs(
|
|
|
600
679
|
|
|
601
680
|
```typescript
|
|
602
681
|
// Only the user can write to their own settings
|
|
603
|
-
await setDoc(doc(db, `appData/${appId}/userData/${userId}/settings/preferences`), {
|
|
682
|
+
await setDoc(doc(db, `appData/${appId}/userData/${userId}/_data/settings/preferences`), {
|
|
604
683
|
_appId: appId,
|
|
605
684
|
_createdAt: serverTimestamp(),
|
|
606
685
|
_createdBy: userId,
|
|
@@ -611,7 +690,7 @@ await setDoc(doc(db, `appData/${appId}/userData/${userId}/settings/preferences`)
|
|
|
611
690
|
|
|
612
691
|
// Only the user can read their own settings
|
|
613
692
|
const settings = await getDoc(
|
|
614
|
-
doc(db, `appData/${appId}/userData/${userId}/settings/preferences`)
|
|
693
|
+
doc(db, `appData/${appId}/userData/${userId}/_data/settings/preferences`)
|
|
615
694
|
);
|
|
616
695
|
```
|
|
617
696
|
|
|
@@ -652,7 +731,7 @@ const snapshot = await getDocs(userPosts);
|
|
|
652
731
|
✅ **Authenticated Users:**
|
|
653
732
|
- Read `publicRead` data
|
|
654
733
|
- Read and write `publicData` (all users' data)
|
|
655
|
-
- Read and write their own `userData/{userId}
|
|
734
|
+
- Read and write their own `userData/{userId}/_data/` only
|
|
656
735
|
- Update/delete documents where `_createdBy == userId`
|
|
657
736
|
|
|
658
737
|
✅ **Guest Users:**
|
|
@@ -716,7 +795,20 @@ import type {
|
|
|
716
795
|
|
|
717
796
|
When using this SDK with LLM-generated code:
|
|
718
797
|
|
|
719
|
-
1.
|
|
798
|
+
1. **🎯 EASIEST: Use `FirestoreHelper` for everything!**
|
|
799
|
+
```typescript
|
|
800
|
+
// ✅ BEST PRACTICE - Let helper handle everything
|
|
801
|
+
const { helper } = await initializeWithToken(tokenResponse);
|
|
802
|
+
await helper.addToPublicData('posts', { title: 'Post', content: 'Hello' });
|
|
803
|
+
|
|
804
|
+
// No need to remember:
|
|
805
|
+
// - Required fields (_appId, _createdAt, _createdBy)
|
|
806
|
+
// - Path construction
|
|
807
|
+
// - Data validation
|
|
808
|
+
// - Soft delete logic
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
2. **🛡️ Use path helper functions to avoid permission-denied errors:**
|
|
720
812
|
```typescript
|
|
721
813
|
import { getPublicDataPath, getUserDataPath } from '@seaverse/data-service-sdk';
|
|
722
814
|
|
|
@@ -728,35 +820,40 @@ When using this SDK with LLM-generated code:
|
|
|
728
820
|
await addDoc(collection(db, `apps/${appId}/posts`), { ... });
|
|
729
821
|
```
|
|
730
822
|
|
|
731
|
-
|
|
823
|
+
3. **⚠️ Never use field names starting with `_`:**
|
|
732
824
|
```typescript
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
825
|
+
// ❌ WRONG - Reserved field
|
|
826
|
+
{ _myField: 'value' }
|
|
827
|
+
|
|
828
|
+
// ✅ CORRECT
|
|
829
|
+
{ myField: 'value' }
|
|
738
830
|
```
|
|
739
831
|
|
|
740
|
-
|
|
832
|
+
4. **🗑️ Use soft delete instead of hard delete:**
|
|
741
833
|
```typescript
|
|
742
|
-
//
|
|
743
|
-
|
|
834
|
+
// ✅ RECOMMENDED
|
|
835
|
+
await helper.softDeleteDoc(path, docId);
|
|
744
836
|
|
|
745
|
-
//
|
|
746
|
-
|
|
837
|
+
// ❌ Only for admins
|
|
838
|
+
await helper.deleteDoc(path, docId);
|
|
839
|
+
```
|
|
747
840
|
|
|
748
|
-
|
|
749
|
-
|
|
841
|
+
5. **✅ Validation is automatic, but you can validate manually if needed:**
|
|
842
|
+
```typescript
|
|
843
|
+
import { validateFirestoreData } from '@seaverse/data-service-sdk';
|
|
750
844
|
|
|
751
|
-
|
|
752
|
-
|
|
845
|
+
try {
|
|
846
|
+
validateFirestoreData(myData);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
console.error('Invalid data:', error.message);
|
|
849
|
+
}
|
|
753
850
|
```
|
|
754
851
|
|
|
755
|
-
|
|
852
|
+
6. **Handle token expiration:**
|
|
756
853
|
- Tokens expire after 1 hour (3600 seconds)
|
|
757
854
|
- Check `expires_in` field and refresh when needed
|
|
758
855
|
|
|
759
|
-
|
|
856
|
+
7. **Use serverTimestamp() for timestamps:**
|
|
760
857
|
```typescript
|
|
761
858
|
import { serverTimestamp } from 'firebase/firestore';
|
|
762
859
|
|
|
@@ -765,7 +862,7 @@ When using this SDK with LLM-generated code:
|
|
|
765
862
|
}
|
|
766
863
|
```
|
|
767
864
|
|
|
768
|
-
|
|
865
|
+
8. **Separate guest and authenticated flows:**
|
|
769
866
|
- Use `generateGuestFirestoreToken()` for anonymous users
|
|
770
867
|
- Use `generateFirestoreToken()` for logged-in users
|
|
771
868
|
|
|
@@ -837,7 +934,7 @@ async function completeExample() {
|
|
|
837
934
|
|
|
838
935
|
// 7. Save user preferences (private)
|
|
839
936
|
await addDoc(
|
|
840
|
-
collection(db, `appData/${appId}/userData/${userId}/preferences`),
|
|
937
|
+
collection(db, `appData/${appId}/userData/${userId}/_data/preferences`),
|
|
841
938
|
{
|
|
842
939
|
_appId: appId,
|
|
843
940
|
_createdAt: serverTimestamp(),
|