@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.
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SeaVerseDataService = {}));
5
- })(this, (function (exports) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('firebase/firestore')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'firebase/firestore'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SeaVerseDataService = {}, global.firestore));
5
+ })(this, (function (exports, firestore) { 'use strict';
6
6
 
7
7
  /**
8
8
  * Create a bound version of a function with a specified `this` context
@@ -4179,6 +4179,628 @@
4179
4179
  }
4180
4180
  }
4181
4181
 
4182
+ /**
4183
+ * Firestore Path Helper Functions
4184
+ *
4185
+ * These helper functions generate correct Firestore paths that match
4186
+ * the security rules. Use these instead of manually constructing paths
4187
+ * to avoid permission-denied errors.
4188
+ *
4189
+ * 🚨 IMPORTANT: These paths are designed to work with SeaVerse Firestore Rules
4190
+ *
4191
+ * Permission Levels:
4192
+ * - publicRead: Read-only for all users, write for admins only
4193
+ * - publicData: Read/write for all authenticated users
4194
+ * - userData: Read/write only for the data owner
4195
+ */
4196
+ /**
4197
+ * Generate path for publicRead data (read-only for users, write for admins)
4198
+ *
4199
+ * @param appId - Your application ID
4200
+ * @param collection - Collection name (e.g., 'announcements', 'config')
4201
+ * @returns Firestore path string
4202
+ *
4203
+ * @example
4204
+ * ```typescript
4205
+ * // Read system announcements
4206
+ * const path = getPublicReadPath('my-app', 'announcements');
4207
+ * // Returns: 'appData/my-app/publicRead/announcements'
4208
+ *
4209
+ * const snapshot = await getDocs(collection(db, path));
4210
+ * ```
4211
+ */
4212
+ function getPublicReadPath(appId, collectionName) {
4213
+ validateSegment('appId', appId);
4214
+ validateSegment('collectionName', collectionName);
4215
+ return `appData/${appId}/publicRead/${collectionName}`;
4216
+ }
4217
+ /**
4218
+ * Generate path for publicData (read/write for all authenticated users)
4219
+ *
4220
+ * @param appId - Your application ID
4221
+ * @param collection - Collection name (e.g., 'posts', 'comments')
4222
+ * @returns Firestore path string
4223
+ *
4224
+ * @example
4225
+ * ```typescript
4226
+ * // Write a public post
4227
+ * const path = getPublicDataPath('my-app', 'posts');
4228
+ * // Returns: 'appData/my-app/publicData/posts'
4229
+ *
4230
+ * await addDoc(collection(db, path), {
4231
+ * _appId: appId,
4232
+ * _createdAt: serverTimestamp(),
4233
+ * _createdBy: userId,
4234
+ * title: 'My Post'
4235
+ * });
4236
+ * ```
4237
+ */
4238
+ function getPublicDataPath(appId, collectionName) {
4239
+ validateSegment('appId', appId);
4240
+ validateSegment('collectionName', collectionName);
4241
+ return `appData/${appId}/publicData/${collectionName}`;
4242
+ }
4243
+ /**
4244
+ * Generate path for userData (private, read/write only by owner)
4245
+ *
4246
+ * @param appId - Your application ID
4247
+ * @param userId - User ID who owns this data
4248
+ * @param collection - Collection name (e.g., 'notes', 'settings')
4249
+ * @returns Firestore path string
4250
+ *
4251
+ * @example
4252
+ * ```typescript
4253
+ * // Write private user notes
4254
+ * const path = getUserDataPath('my-app', 'user-123', 'notes');
4255
+ * // Returns: 'appData/my-app/userData/user-123/notes'
4256
+ *
4257
+ * await addDoc(collection(db, path), {
4258
+ * _appId: appId,
4259
+ * _createdAt: serverTimestamp(),
4260
+ * _createdBy: userId,
4261
+ * content: 'Private note'
4262
+ * });
4263
+ * ```
4264
+ */
4265
+ function getUserDataPath(appId, userId, collectionName) {
4266
+ validateSegment('appId', appId);
4267
+ validateSegment('userId', userId);
4268
+ validateSegment('collectionName', collectionName);
4269
+ return `appData/${appId}/userData/${userId}/${collectionName}`;
4270
+ }
4271
+ /**
4272
+ * Generate path for a specific document in publicRead
4273
+ *
4274
+ * @param appId - Your application ID
4275
+ * @param collection - Collection name
4276
+ * @param docId - Document ID
4277
+ * @returns Firestore document path string
4278
+ *
4279
+ * @example
4280
+ * ```typescript
4281
+ * const path = getPublicReadDocPath('my-app', 'announcements', 'announcement-1');
4282
+ * // Returns: 'appData/my-app/publicRead/announcements/announcement-1'
4283
+ *
4284
+ * const docSnap = await getDoc(doc(db, path));
4285
+ * ```
4286
+ */
4287
+ function getPublicReadDocPath(appId, collectionName, docId) {
4288
+ validateSegment('appId', appId);
4289
+ validateSegment('collectionName', collectionName);
4290
+ validateSegment('docId', docId);
4291
+ return `appData/${appId}/publicRead/${collectionName}/${docId}`;
4292
+ }
4293
+ /**
4294
+ * Generate path for a specific document in publicData
4295
+ *
4296
+ * @param appId - Your application ID
4297
+ * @param collection - Collection name
4298
+ * @param docId - Document ID
4299
+ * @returns Firestore document path string
4300
+ *
4301
+ * @example
4302
+ * ```typescript
4303
+ * const path = getPublicDataDocPath('my-app', 'posts', 'post-123');
4304
+ * // Returns: 'appData/my-app/publicData/posts/post-123'
4305
+ *
4306
+ * const docSnap = await getDoc(doc(db, path));
4307
+ * ```
4308
+ */
4309
+ function getPublicDataDocPath(appId, collectionName, docId) {
4310
+ validateSegment('appId', appId);
4311
+ validateSegment('collectionName', collectionName);
4312
+ validateSegment('docId', docId);
4313
+ return `appData/${appId}/publicData/${collectionName}/${docId}`;
4314
+ }
4315
+ /**
4316
+ * Generate path for a specific document in userData
4317
+ *
4318
+ * @param appId - Your application ID
4319
+ * @param userId - User ID who owns this data
4320
+ * @param collection - Collection name
4321
+ * @param docId - Document ID
4322
+ * @returns Firestore document path string
4323
+ *
4324
+ * @example
4325
+ * ```typescript
4326
+ * const path = getUserDataDocPath('my-app', 'user-123', 'notes', 'note-456');
4327
+ * // Returns: 'appData/my-app/userData/user-123/notes/note-456'
4328
+ *
4329
+ * const docSnap = await getDoc(doc(db, path));
4330
+ * ```
4331
+ */
4332
+ function getUserDataDocPath(appId, userId, collectionName, docId) {
4333
+ validateSegment('appId', appId);
4334
+ validateSegment('userId', userId);
4335
+ validateSegment('collectionName', collectionName);
4336
+ validateSegment('docId', docId);
4337
+ return `appData/${appId}/userData/${userId}/${collectionName}/${docId}`;
4338
+ }
4339
+ /**
4340
+ * Validate a path segment to ensure it doesn't contain invalid characters
4341
+ *
4342
+ * @param name - Parameter name for error messages
4343
+ * @param value - The segment value to validate
4344
+ * @throws Error if the segment is invalid
4345
+ */
4346
+ function validateSegment(name, value) {
4347
+ if (!value || typeof value !== 'string') {
4348
+ throw new Error(`${name} must be a non-empty string`);
4349
+ }
4350
+ if (value.includes('/')) {
4351
+ throw new Error(`${name} cannot contain forward slashes (/). Got: "${value}"`);
4352
+ }
4353
+ if (value.trim() !== value) {
4354
+ throw new Error(`${name} cannot start or end with whitespace. Got: "${value}"`);
4355
+ }
4356
+ }
4357
+ /**
4358
+ * Path builder for advanced use cases
4359
+ * Provides a fluent interface for building Firestore paths
4360
+ *
4361
+ * @example
4362
+ * ```typescript
4363
+ * // Build a path step by step
4364
+ * const builder = new PathBuilder('my-app');
4365
+ * const path = builder.publicData('posts').build();
4366
+ * // Returns: 'appData/my-app/publicData/posts'
4367
+ *
4368
+ * // With document ID
4369
+ * const docPath = builder.publicData('posts').doc('post-123').build();
4370
+ * // Returns: 'appData/my-app/publicData/posts/post-123'
4371
+ * ```
4372
+ */
4373
+ class PathBuilder {
4374
+ constructor(appId) {
4375
+ this.appId = appId;
4376
+ this.segments = ['appData'];
4377
+ validateSegment('appId', appId);
4378
+ this.segments.push(appId);
4379
+ }
4380
+ /**
4381
+ * Add publicRead collection to path
4382
+ */
4383
+ publicRead(collectionName) {
4384
+ validateSegment('collectionName', collectionName);
4385
+ this.segments.push('publicRead', collectionName);
4386
+ return this;
4387
+ }
4388
+ /**
4389
+ * Add publicData collection to path
4390
+ */
4391
+ publicData(collectionName) {
4392
+ validateSegment('collectionName', collectionName);
4393
+ this.segments.push('publicData', collectionName);
4394
+ return this;
4395
+ }
4396
+ /**
4397
+ * Add userData collection to path
4398
+ */
4399
+ userData(userId, collectionName) {
4400
+ validateSegment('userId', userId);
4401
+ validateSegment('collectionName', collectionName);
4402
+ this.segments.push('userData', userId, collectionName);
4403
+ return this;
4404
+ }
4405
+ /**
4406
+ * Add document ID to path
4407
+ */
4408
+ doc(docId) {
4409
+ validateSegment('docId', docId);
4410
+ this.segments.push(docId);
4411
+ return this;
4412
+ }
4413
+ /**
4414
+ * Build the final path string
4415
+ */
4416
+ build() {
4417
+ return this.segments.join('/');
4418
+ }
4419
+ }
4420
+ /**
4421
+ * Common path patterns as constants for frequently used paths
4422
+ */
4423
+ const PATH_PATTERNS = {
4424
+ /**
4425
+ * Base pattern for all SeaVerse data
4426
+ */
4427
+ APP_DATA: 'appData',
4428
+ /**
4429
+ * Permission layers
4430
+ */
4431
+ PUBLIC_READ: 'publicRead',
4432
+ PUBLIC_DATA: 'publicData',
4433
+ USER_DATA: 'userData',
4434
+ };
4435
+
4436
+ /**
4437
+ * Firestore Helper - LLM-Friendly Firestore Operations
4438
+ *
4439
+ * This helper class automatically handles required fields (_appId, _createdAt, _createdBy)
4440
+ * so LLM doesn't need to remember them. It provides a simple, high-level API.
4441
+ *
4442
+ * 🎯 LLM-FIRST DESIGN:
4443
+ * - No need to remember required fields
4444
+ * - No need to construct paths manually
4445
+ * - No need to import serverTimestamp
4446
+ * - One method does everything
4447
+ */
4448
+ /**
4449
+ * Firestore operations helper with automatic metadata injection
4450
+ *
4451
+ * This class wraps Firestore operations and automatically adds required fields:
4452
+ * - _appId: Application ID (for data isolation)
4453
+ * - _createdAt: Server timestamp (for creation time)
4454
+ * - _createdBy: User ID (for ownership tracking)
4455
+ *
4456
+ * @example
4457
+ * ```typescript
4458
+ * const helper = new FirestoreHelper(db, appId, userId);
4459
+ *
4460
+ * // ✅ LLM-friendly: Just pass your data, required fields auto-injected
4461
+ * await helper.addToPublicData('posts', {
4462
+ * title: 'My Post',
4463
+ * content: 'Hello world'
4464
+ * });
4465
+ *
4466
+ * // ❌ OLD WAY: LLM needs to remember 3 required fields
4467
+ * await addDoc(collection(db, path), {
4468
+ * _appId: appId,
4469
+ * _createdAt: serverTimestamp(),
4470
+ * _createdBy: userId,
4471
+ * title: 'My Post',
4472
+ * content: 'Hello world'
4473
+ * });
4474
+ * ```
4475
+ */
4476
+ class FirestoreHelper {
4477
+ constructor(db, appId, userId) {
4478
+ this.db = db;
4479
+ this.appId = appId;
4480
+ this.userId = userId;
4481
+ }
4482
+ /**
4483
+ * Add document to publicData (shared, read/write for all users)
4484
+ *
4485
+ * Automatically injects: _appId, _createdAt, _createdBy
4486
+ *
4487
+ * @param collectionName - Collection name (e.g., 'posts', 'comments')
4488
+ * @param data - Your business data
4489
+ * @returns Document reference
4490
+ *
4491
+ * @example
4492
+ * ```typescript
4493
+ * // ✅ LLM-friendly: Simple and clean
4494
+ * const docRef = await helper.addToPublicData('posts', {
4495
+ * title: 'My First Post',
4496
+ * content: 'Hello world'
4497
+ * });
4498
+ * console.log('Created post:', docRef.id);
4499
+ * ```
4500
+ */
4501
+ async addToPublicData(collectionName, data) {
4502
+ const path = getPublicDataPath(this.appId, collectionName);
4503
+ return this.addDocWithMeta(path, data);
4504
+ }
4505
+ /**
4506
+ * Add document to userData (private, only owner can read/write)
4507
+ *
4508
+ * Automatically injects: _appId, _createdAt, _createdBy
4509
+ *
4510
+ * @param collectionName - Collection name (e.g., 'notes', 'settings')
4511
+ * @param data - Your business data
4512
+ * @returns Document reference
4513
+ *
4514
+ * @example
4515
+ * ```typescript
4516
+ * // ✅ LLM-friendly: Private data, auto-isolated
4517
+ * await helper.addToUserData('notes', {
4518
+ * title: 'Private Note',
4519
+ * content: 'Only I can see this'
4520
+ * });
4521
+ * ```
4522
+ */
4523
+ async addToUserData(collectionName, data) {
4524
+ const path = getUserDataPath(this.appId, this.userId, collectionName);
4525
+ return this.addDocWithMeta(path, data);
4526
+ }
4527
+ /**
4528
+ * Get all documents from publicData collection
4529
+ *
4530
+ * @param collectionName - Collection name
4531
+ * @returns QuerySnapshot with documents
4532
+ *
4533
+ * @example
4534
+ * ```typescript
4535
+ * const snapshot = await helper.getPublicData('posts');
4536
+ * snapshot.forEach(doc => {
4537
+ * console.log(doc.id, doc.data());
4538
+ * });
4539
+ * ```
4540
+ */
4541
+ async getPublicData(collectionName) {
4542
+ const path = getPublicDataPath(this.appId, collectionName);
4543
+ return this.getDocs(path);
4544
+ }
4545
+ /**
4546
+ * Get all documents from userData collection (user's private data)
4547
+ *
4548
+ * @param collectionName - Collection name
4549
+ * @returns QuerySnapshot with documents
4550
+ *
4551
+ * @example
4552
+ * ```typescript
4553
+ * const snapshot = await helper.getUserData('notes');
4554
+ * snapshot.forEach(doc => {
4555
+ * console.log('My note:', doc.data());
4556
+ * });
4557
+ * ```
4558
+ */
4559
+ async getUserData(collectionName) {
4560
+ const path = getUserDataPath(this.appId, this.userId, collectionName);
4561
+ return this.getDocs(path);
4562
+ }
4563
+ /**
4564
+ * Get all documents from publicRead collection (read-only for users)
4565
+ *
4566
+ * @param collectionName - Collection name
4567
+ * @returns QuerySnapshot with documents
4568
+ *
4569
+ * @example
4570
+ * ```typescript
4571
+ * const configs = await helper.getPublicRead('config');
4572
+ * ```
4573
+ */
4574
+ async getPublicRead(collectionName) {
4575
+ const path = getPublicReadPath(this.appId, collectionName);
4576
+ return this.getDocs(path);
4577
+ }
4578
+ /**
4579
+ * Get collection reference for publicData
4580
+ * Use this for advanced queries with where(), orderBy(), limit()
4581
+ *
4582
+ * @param collectionName - Collection name
4583
+ * @returns Collection reference
4584
+ *
4585
+ * @example
4586
+ * ```typescript
4587
+ * import { query, where, orderBy } from 'firebase/firestore';
4588
+ *
4589
+ * const postsRef = helper.publicDataCollection('posts');
4590
+ * const q = query(
4591
+ * postsRef,
4592
+ * where('_createdBy', '==', userId),
4593
+ * orderBy('_createdAt', 'desc')
4594
+ * );
4595
+ * const snapshot = await getDocs(q);
4596
+ * ```
4597
+ */
4598
+ publicDataCollection(collectionName) {
4599
+ const path = getPublicDataPath(this.appId, collectionName);
4600
+ return this.getCollection(path);
4601
+ }
4602
+ /**
4603
+ * Get collection reference for userData
4604
+ *
4605
+ * @param collectionName - Collection name
4606
+ * @returns Collection reference
4607
+ */
4608
+ userDataCollection(collectionName) {
4609
+ const path = getUserDataPath(this.appId, this.userId, collectionName);
4610
+ return this.getCollection(path);
4611
+ }
4612
+ /**
4613
+ * Get collection reference for publicRead
4614
+ *
4615
+ * @param collectionName - Collection name
4616
+ * @returns Collection reference
4617
+ */
4618
+ publicReadCollection(collectionName) {
4619
+ const path = getPublicReadPath(this.appId, collectionName);
4620
+ return this.getCollection(path);
4621
+ }
4622
+ /**
4623
+ * Update document with automatic metadata update
4624
+ *
4625
+ * Automatically sets: _updatedAt, _updatedBy
4626
+ *
4627
+ * @param collectionPath - Full collection path
4628
+ * @param docId - Document ID
4629
+ * @param data - Data to update
4630
+ *
4631
+ * @example
4632
+ * ```typescript
4633
+ * await helper.updateDoc(
4634
+ * getPublicDataPath(appId, 'posts'),
4635
+ * 'post-123',
4636
+ * { title: 'Updated Title' }
4637
+ * );
4638
+ * ```
4639
+ */
4640
+ async updateDoc(collectionPath, docId, data) {
4641
+ const { updateDoc, doc, serverTimestamp } = await this.loadFirestore();
4642
+ const docRef = doc(this.db, collectionPath, docId);
4643
+ return updateDoc(docRef, {
4644
+ ...data,
4645
+ _updatedAt: serverTimestamp(),
4646
+ _updatedBy: this.userId
4647
+ });
4648
+ }
4649
+ /**
4650
+ * Delete document
4651
+ *
4652
+ * @param collectionPath - Full collection path
4653
+ * @param docId - Document ID
4654
+ */
4655
+ async deleteDoc(collectionPath, docId) {
4656
+ const { deleteDoc, doc } = await this.loadFirestore();
4657
+ const docRef = doc(this.db, collectionPath, docId);
4658
+ return deleteDoc(docRef);
4659
+ }
4660
+ /**
4661
+ * Get single document by ID
4662
+ *
4663
+ * @param collectionPath - Full collection path
4664
+ * @param docId - Document ID
4665
+ *
4666
+ * @example
4667
+ * ```typescript
4668
+ * const docSnap = await helper.getDoc(
4669
+ * getPublicDataPath(appId, 'posts'),
4670
+ * 'post-123'
4671
+ * );
4672
+ * if (docSnap.exists()) {
4673
+ * console.log(docSnap.data());
4674
+ * }
4675
+ * ```
4676
+ */
4677
+ async getDoc(collectionPath, docId) {
4678
+ const { getDoc, doc } = await this.loadFirestore();
4679
+ const docRef = doc(this.db, collectionPath, docId);
4680
+ return getDoc(docRef);
4681
+ }
4682
+ // ============================================================================
4683
+ // Private Helper Methods
4684
+ // ============================================================================
4685
+ /**
4686
+ * Internal: Add document with metadata injection
4687
+ */
4688
+ async addDocWithMeta(collectionPath, data) {
4689
+ const { addDoc, collection, serverTimestamp } = await this.loadFirestore();
4690
+ const colRef = collection(this.db, collectionPath);
4691
+ return addDoc(colRef, {
4692
+ _appId: this.appId,
4693
+ _createdAt: serverTimestamp(),
4694
+ _createdBy: this.userId,
4695
+ ...data
4696
+ });
4697
+ }
4698
+ /**
4699
+ * Internal: Get all documents from collection
4700
+ */
4701
+ async getDocs(collectionPath) {
4702
+ const { getDocs, collection } = await this.loadFirestore();
4703
+ const colRef = collection(this.db, collectionPath);
4704
+ return getDocs(colRef);
4705
+ }
4706
+ /**
4707
+ * Internal: Get collection reference
4708
+ */
4709
+ getCollection(collectionPath) {
4710
+ // Note: This is sync, so we can't use dynamic import
4711
+ // We assume firebase/firestore is already loaded by initializeWithToken
4712
+ const { collection } = require('firebase/firestore');
4713
+ return collection(this.db, collectionPath);
4714
+ }
4715
+ /**
4716
+ * Internal: Lazy load Firebase Firestore functions
4717
+ */
4718
+ async loadFirestore() {
4719
+ const firestore = await import('firebase/firestore');
4720
+ return {
4721
+ collection: firestore.collection,
4722
+ doc: firestore.doc,
4723
+ addDoc: firestore.addDoc,
4724
+ setDoc: firestore.setDoc,
4725
+ getDoc: firestore.getDoc,
4726
+ getDocs: firestore.getDocs,
4727
+ updateDoc: firestore.updateDoc,
4728
+ deleteDoc: firestore.deleteDoc,
4729
+ serverTimestamp: firestore.serverTimestamp,
4730
+ query: firestore.query,
4731
+ where: firestore.where,
4732
+ orderBy: firestore.orderBy,
4733
+ limit: firestore.limit
4734
+ };
4735
+ }
4736
+ }
4737
+ /**
4738
+ * Standalone helper function: Add document with automatic metadata injection
4739
+ *
4740
+ * Use this if you don't want to create a FirestoreHelper instance.
4741
+ *
4742
+ * @param db - Firestore instance
4743
+ * @param collectionPath - Full collection path
4744
+ * @param appId - Application ID
4745
+ * @param userId - User ID
4746
+ * @param data - Your business data
4747
+ * @returns Document reference
4748
+ *
4749
+ * @example
4750
+ * ```typescript
4751
+ * import { addDocWithMeta, getPublicDataPath } from '@seaverse/data-service-sdk';
4752
+ *
4753
+ * const docRef = await addDocWithMeta(
4754
+ * db,
4755
+ * getPublicDataPath(appId, 'posts'),
4756
+ * appId,
4757
+ * userId,
4758
+ * { title: 'Post', content: 'Hello' }
4759
+ * );
4760
+ * ```
4761
+ */
4762
+ async function addDocWithMeta(db, collectionPath, appId, userId, data) {
4763
+ const { addDoc, collection, serverTimestamp } = await import('firebase/firestore');
4764
+ const colRef = collection(db, collectionPath);
4765
+ return addDoc(colRef, {
4766
+ _appId: appId,
4767
+ _createdAt: serverTimestamp(),
4768
+ _createdBy: userId,
4769
+ ...data
4770
+ });
4771
+ }
4772
+ /**
4773
+ * Standalone helper function: Update document with automatic metadata
4774
+ *
4775
+ * @param db - Firestore instance
4776
+ * @param collectionPath - Full collection path
4777
+ * @param docId - Document ID
4778
+ * @param userId - User ID (for _updatedBy field)
4779
+ * @param data - Data to update
4780
+ *
4781
+ * @example
4782
+ * ```typescript
4783
+ * import { updateDocWithMeta, getPublicDataPath } from '@seaverse/data-service-sdk';
4784
+ *
4785
+ * await updateDocWithMeta(
4786
+ * db,
4787
+ * getPublicDataPath(appId, 'posts'),
4788
+ * 'post-123',
4789
+ * userId,
4790
+ * { title: 'Updated Title' }
4791
+ * );
4792
+ * ```
4793
+ */
4794
+ async function updateDocWithMeta(db, collectionPath, docId, userId, data) {
4795
+ const { updateDoc, doc, serverTimestamp } = await import('firebase/firestore');
4796
+ const docRef = doc(db, collectionPath, docId);
4797
+ return updateDoc(docRef, {
4798
+ ...data,
4799
+ _updatedAt: serverTimestamp(),
4800
+ _updatedBy: userId
4801
+ });
4802
+ }
4803
+
4182
4804
  /**
4183
4805
  * Create Firebase configuration from Firestore token response
4184
4806
  *
@@ -4212,23 +4834,32 @@
4212
4834
  * 1. Creates Firebase config from token response
4213
4835
  * 2. Initializes Firebase app
4214
4836
  * 3. Signs in with the custom token
4215
- * 4. Returns authenticated Firebase instances
4837
+ * 4. Creates FirestoreHelper for LLM-friendly operations
4838
+ * 5. Returns authenticated Firebase instances
4216
4839
  *
4217
4840
  * IMPORTANT: This function requires Firebase SDK to be installed separately:
4218
4841
  * npm install firebase
4219
4842
  *
4843
+ * 🎯 LLM RECOMMENDED: Use the `helper` object for simplified operations
4844
+ *
4220
4845
  * @param tokenResponse - The Firestore token response from SDK
4221
- * @returns Object containing initialized Firebase app, auth, and db instances
4846
+ * @returns Object containing initialized Firebase instances and LLM-friendly helper
4222
4847
  *
4223
4848
  * @example
4224
4849
  * ```typescript
4225
4850
  * import { initializeWithToken } from '@seaverse/data-service-sdk';
4226
4851
  *
4227
4852
  * const tokenResponse = await client.generateGuestFirestoreToken({ app_id: 'my-app' });
4228
- * const { app, auth, db, userId, appId } = await initializeWithToken(tokenResponse);
4853
+ * const { db, userId, appId, helper } = await initializeWithToken(tokenResponse);
4229
4854
  *
4230
- * // Ready to use Firestore!
4231
- * const snapshot = await getDocs(collection(db, `appData/${appId}/publicData/posts`));
4855
+ * // LLM-FRIENDLY: Use helper (automatic required fields)
4856
+ * await helper.addToPublicData('posts', {
4857
+ * title: 'My Post',
4858
+ * content: 'Hello'
4859
+ * });
4860
+ *
4861
+ * // ❌ OLD WAY: Manual (need to remember required fields)
4862
+ * // await addDoc(collection(db, path), { _appId, _createdAt, _createdBy, ...data });
4232
4863
  * ```
4233
4864
  */
4234
4865
  async function initializeWithToken(tokenResponse) {
@@ -4260,21 +4891,145 @@
4260
4891
  // Get Firestore instance with correct database ID
4261
4892
  // IMPORTANT: Must specify database_id, not just use default!
4262
4893
  const db = getFirestore(app, tokenResponse.database_id);
4894
+ const userId = tokenResponse.user_id;
4895
+ const appId = tokenResponse.app_id || '';
4896
+ // Create LLM-friendly helper
4897
+ const helper = new FirestoreHelper(db, appId, userId);
4263
4898
  return {
4264
4899
  app,
4265
4900
  auth,
4266
4901
  db,
4267
- userId: tokenResponse.user_id,
4268
- appId: tokenResponse.app_id || '',
4902
+ userId,
4903
+ appId,
4904
+ helper,
4269
4905
  };
4270
4906
  }
4271
4907
 
4908
+ Object.defineProperty(exports, "addDoc", {
4909
+ enumerable: true,
4910
+ get: function () { return firestore.addDoc; }
4911
+ });
4912
+ Object.defineProperty(exports, "arrayRemove", {
4913
+ enumerable: true,
4914
+ get: function () { return firestore.arrayRemove; }
4915
+ });
4916
+ Object.defineProperty(exports, "arrayUnion", {
4917
+ enumerable: true,
4918
+ get: function () { return firestore.arrayUnion; }
4919
+ });
4920
+ Object.defineProperty(exports, "collection", {
4921
+ enumerable: true,
4922
+ get: function () { return firestore.collection; }
4923
+ });
4924
+ Object.defineProperty(exports, "collectionGroup", {
4925
+ enumerable: true,
4926
+ get: function () { return firestore.collectionGroup; }
4927
+ });
4928
+ Object.defineProperty(exports, "deleteDoc", {
4929
+ enumerable: true,
4930
+ get: function () { return firestore.deleteDoc; }
4931
+ });
4932
+ Object.defineProperty(exports, "deleteField", {
4933
+ enumerable: true,
4934
+ get: function () { return firestore.deleteField; }
4935
+ });
4936
+ Object.defineProperty(exports, "doc", {
4937
+ enumerable: true,
4938
+ get: function () { return firestore.doc; }
4939
+ });
4940
+ Object.defineProperty(exports, "endAt", {
4941
+ enumerable: true,
4942
+ get: function () { return firestore.endAt; }
4943
+ });
4944
+ Object.defineProperty(exports, "endBefore", {
4945
+ enumerable: true,
4946
+ get: function () { return firestore.endBefore; }
4947
+ });
4948
+ Object.defineProperty(exports, "getDoc", {
4949
+ enumerable: true,
4950
+ get: function () { return firestore.getDoc; }
4951
+ });
4952
+ Object.defineProperty(exports, "getDocs", {
4953
+ enumerable: true,
4954
+ get: function () { return firestore.getDocs; }
4955
+ });
4956
+ Object.defineProperty(exports, "getFirestore", {
4957
+ enumerable: true,
4958
+ get: function () { return firestore.getFirestore; }
4959
+ });
4960
+ Object.defineProperty(exports, "increment", {
4961
+ enumerable: true,
4962
+ get: function () { return firestore.increment; }
4963
+ });
4964
+ Object.defineProperty(exports, "limit", {
4965
+ enumerable: true,
4966
+ get: function () { return firestore.limit; }
4967
+ });
4968
+ Object.defineProperty(exports, "limitToLast", {
4969
+ enumerable: true,
4970
+ get: function () { return firestore.limitToLast; }
4971
+ });
4972
+ Object.defineProperty(exports, "onSnapshot", {
4973
+ enumerable: true,
4974
+ get: function () { return firestore.onSnapshot; }
4975
+ });
4976
+ Object.defineProperty(exports, "orderBy", {
4977
+ enumerable: true,
4978
+ get: function () { return firestore.orderBy; }
4979
+ });
4980
+ Object.defineProperty(exports, "query", {
4981
+ enumerable: true,
4982
+ get: function () { return firestore.query; }
4983
+ });
4984
+ Object.defineProperty(exports, "runTransaction", {
4985
+ enumerable: true,
4986
+ get: function () { return firestore.runTransaction; }
4987
+ });
4988
+ Object.defineProperty(exports, "serverTimestamp", {
4989
+ enumerable: true,
4990
+ get: function () { return firestore.serverTimestamp; }
4991
+ });
4992
+ Object.defineProperty(exports, "setDoc", {
4993
+ enumerable: true,
4994
+ get: function () { return firestore.setDoc; }
4995
+ });
4996
+ Object.defineProperty(exports, "startAfter", {
4997
+ enumerable: true,
4998
+ get: function () { return firestore.startAfter; }
4999
+ });
5000
+ Object.defineProperty(exports, "startAt", {
5001
+ enumerable: true,
5002
+ get: function () { return firestore.startAt; }
5003
+ });
5004
+ Object.defineProperty(exports, "updateDoc", {
5005
+ enumerable: true,
5006
+ get: function () { return firestore.updateDoc; }
5007
+ });
5008
+ Object.defineProperty(exports, "where", {
5009
+ enumerable: true,
5010
+ get: function () { return firestore.where; }
5011
+ });
5012
+ Object.defineProperty(exports, "writeBatch", {
5013
+ enumerable: true,
5014
+ get: function () { return firestore.writeBatch; }
5015
+ });
4272
5016
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
4273
5017
  exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
4274
5018
  exports.DataServiceClient = DataServiceClient;
4275
5019
  exports.ENDPOINTS = ENDPOINTS;
5020
+ exports.FirestoreHelper = FirestoreHelper;
5021
+ exports.PATH_PATTERNS = PATH_PATTERNS;
5022
+ exports.PathBuilder = PathBuilder;
5023
+ exports.addDocWithMeta = addDocWithMeta;
4276
5024
  exports.getFirebaseConfig = getFirebaseConfig;
5025
+ exports.getPublicDataDocPath = getPublicDataDocPath;
5026
+ exports.getPublicDataPath = getPublicDataPath;
5027
+ exports.getPublicReadDocPath = getPublicReadDocPath;
5028
+ exports.getPublicReadPath = getPublicReadPath;
5029
+ exports.getUserDataDocPath = getUserDataDocPath;
5030
+ exports.getUserDataPath = getUserDataPath;
4277
5031
  exports.initializeWithToken = initializeWithToken;
5032
+ exports.updateDocWithMeta = updateDocWithMeta;
4278
5033
 
4279
5034
  }));
4280
5035
  //# sourceMappingURL=browser.umd.js.map