@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/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var axios = require('axios');
4
+ var firestore = require('firebase/firestore');
4
5
 
5
6
  /**
6
7
  * Data Service SDK configuration
@@ -265,6 +266,628 @@ class DataServiceClient {
265
266
  }
266
267
  }
267
268
 
269
+ /**
270
+ * Firestore Path Helper Functions
271
+ *
272
+ * These helper functions generate correct Firestore paths that match
273
+ * the security rules. Use these instead of manually constructing paths
274
+ * to avoid permission-denied errors.
275
+ *
276
+ * 🚨 IMPORTANT: These paths are designed to work with SeaVerse Firestore Rules
277
+ *
278
+ * Permission Levels:
279
+ * - publicRead: Read-only for all users, write for admins only
280
+ * - publicData: Read/write for all authenticated users
281
+ * - userData: Read/write only for the data owner
282
+ */
283
+ /**
284
+ * Generate path for publicRead data (read-only for users, write for admins)
285
+ *
286
+ * @param appId - Your application ID
287
+ * @param collection - Collection name (e.g., 'announcements', 'config')
288
+ * @returns Firestore path string
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * // Read system announcements
293
+ * const path = getPublicReadPath('my-app', 'announcements');
294
+ * // Returns: 'appData/my-app/publicRead/announcements'
295
+ *
296
+ * const snapshot = await getDocs(collection(db, path));
297
+ * ```
298
+ */
299
+ function getPublicReadPath(appId, collectionName) {
300
+ validateSegment('appId', appId);
301
+ validateSegment('collectionName', collectionName);
302
+ return `appData/${appId}/publicRead/${collectionName}`;
303
+ }
304
+ /**
305
+ * Generate path for publicData (read/write for all authenticated users)
306
+ *
307
+ * @param appId - Your application ID
308
+ * @param collection - Collection name (e.g., 'posts', 'comments')
309
+ * @returns Firestore path string
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * // Write a public post
314
+ * const path = getPublicDataPath('my-app', 'posts');
315
+ * // Returns: 'appData/my-app/publicData/posts'
316
+ *
317
+ * await addDoc(collection(db, path), {
318
+ * _appId: appId,
319
+ * _createdAt: serverTimestamp(),
320
+ * _createdBy: userId,
321
+ * title: 'My Post'
322
+ * });
323
+ * ```
324
+ */
325
+ function getPublicDataPath(appId, collectionName) {
326
+ validateSegment('appId', appId);
327
+ validateSegment('collectionName', collectionName);
328
+ return `appData/${appId}/publicData/${collectionName}`;
329
+ }
330
+ /**
331
+ * Generate path for userData (private, read/write only by owner)
332
+ *
333
+ * @param appId - Your application ID
334
+ * @param userId - User ID who owns this data
335
+ * @param collection - Collection name (e.g., 'notes', 'settings')
336
+ * @returns Firestore path string
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * // Write private user notes
341
+ * const path = getUserDataPath('my-app', 'user-123', 'notes');
342
+ * // Returns: 'appData/my-app/userData/user-123/notes'
343
+ *
344
+ * await addDoc(collection(db, path), {
345
+ * _appId: appId,
346
+ * _createdAt: serverTimestamp(),
347
+ * _createdBy: userId,
348
+ * content: 'Private note'
349
+ * });
350
+ * ```
351
+ */
352
+ function getUserDataPath(appId, userId, collectionName) {
353
+ validateSegment('appId', appId);
354
+ validateSegment('userId', userId);
355
+ validateSegment('collectionName', collectionName);
356
+ return `appData/${appId}/userData/${userId}/${collectionName}`;
357
+ }
358
+ /**
359
+ * Generate path for a specific document in publicRead
360
+ *
361
+ * @param appId - Your application ID
362
+ * @param collection - Collection name
363
+ * @param docId - Document ID
364
+ * @returns Firestore document path string
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * const path = getPublicReadDocPath('my-app', 'announcements', 'announcement-1');
369
+ * // Returns: 'appData/my-app/publicRead/announcements/announcement-1'
370
+ *
371
+ * const docSnap = await getDoc(doc(db, path));
372
+ * ```
373
+ */
374
+ function getPublicReadDocPath(appId, collectionName, docId) {
375
+ validateSegment('appId', appId);
376
+ validateSegment('collectionName', collectionName);
377
+ validateSegment('docId', docId);
378
+ return `appData/${appId}/publicRead/${collectionName}/${docId}`;
379
+ }
380
+ /**
381
+ * Generate path for a specific document in publicData
382
+ *
383
+ * @param appId - Your application ID
384
+ * @param collection - Collection name
385
+ * @param docId - Document ID
386
+ * @returns Firestore document path string
387
+ *
388
+ * @example
389
+ * ```typescript
390
+ * const path = getPublicDataDocPath('my-app', 'posts', 'post-123');
391
+ * // Returns: 'appData/my-app/publicData/posts/post-123'
392
+ *
393
+ * const docSnap = await getDoc(doc(db, path));
394
+ * ```
395
+ */
396
+ function getPublicDataDocPath(appId, collectionName, docId) {
397
+ validateSegment('appId', appId);
398
+ validateSegment('collectionName', collectionName);
399
+ validateSegment('docId', docId);
400
+ return `appData/${appId}/publicData/${collectionName}/${docId}`;
401
+ }
402
+ /**
403
+ * Generate path for a specific document in userData
404
+ *
405
+ * @param appId - Your application ID
406
+ * @param userId - User ID who owns this data
407
+ * @param collection - Collection name
408
+ * @param docId - Document ID
409
+ * @returns Firestore document path string
410
+ *
411
+ * @example
412
+ * ```typescript
413
+ * const path = getUserDataDocPath('my-app', 'user-123', 'notes', 'note-456');
414
+ * // Returns: 'appData/my-app/userData/user-123/notes/note-456'
415
+ *
416
+ * const docSnap = await getDoc(doc(db, path));
417
+ * ```
418
+ */
419
+ function getUserDataDocPath(appId, userId, collectionName, docId) {
420
+ validateSegment('appId', appId);
421
+ validateSegment('userId', userId);
422
+ validateSegment('collectionName', collectionName);
423
+ validateSegment('docId', docId);
424
+ return `appData/${appId}/userData/${userId}/${collectionName}/${docId}`;
425
+ }
426
+ /**
427
+ * Validate a path segment to ensure it doesn't contain invalid characters
428
+ *
429
+ * @param name - Parameter name for error messages
430
+ * @param value - The segment value to validate
431
+ * @throws Error if the segment is invalid
432
+ */
433
+ function validateSegment(name, value) {
434
+ if (!value || typeof value !== 'string') {
435
+ throw new Error(`${name} must be a non-empty string`);
436
+ }
437
+ if (value.includes('/')) {
438
+ throw new Error(`${name} cannot contain forward slashes (/). Got: "${value}"`);
439
+ }
440
+ if (value.trim() !== value) {
441
+ throw new Error(`${name} cannot start or end with whitespace. Got: "${value}"`);
442
+ }
443
+ }
444
+ /**
445
+ * Path builder for advanced use cases
446
+ * Provides a fluent interface for building Firestore paths
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * // Build a path step by step
451
+ * const builder = new PathBuilder('my-app');
452
+ * const path = builder.publicData('posts').build();
453
+ * // Returns: 'appData/my-app/publicData/posts'
454
+ *
455
+ * // With document ID
456
+ * const docPath = builder.publicData('posts').doc('post-123').build();
457
+ * // Returns: 'appData/my-app/publicData/posts/post-123'
458
+ * ```
459
+ */
460
+ class PathBuilder {
461
+ constructor(appId) {
462
+ this.appId = appId;
463
+ this.segments = ['appData'];
464
+ validateSegment('appId', appId);
465
+ this.segments.push(appId);
466
+ }
467
+ /**
468
+ * Add publicRead collection to path
469
+ */
470
+ publicRead(collectionName) {
471
+ validateSegment('collectionName', collectionName);
472
+ this.segments.push('publicRead', collectionName);
473
+ return this;
474
+ }
475
+ /**
476
+ * Add publicData collection to path
477
+ */
478
+ publicData(collectionName) {
479
+ validateSegment('collectionName', collectionName);
480
+ this.segments.push('publicData', collectionName);
481
+ return this;
482
+ }
483
+ /**
484
+ * Add userData collection to path
485
+ */
486
+ userData(userId, collectionName) {
487
+ validateSegment('userId', userId);
488
+ validateSegment('collectionName', collectionName);
489
+ this.segments.push('userData', userId, collectionName);
490
+ return this;
491
+ }
492
+ /**
493
+ * Add document ID to path
494
+ */
495
+ doc(docId) {
496
+ validateSegment('docId', docId);
497
+ this.segments.push(docId);
498
+ return this;
499
+ }
500
+ /**
501
+ * Build the final path string
502
+ */
503
+ build() {
504
+ return this.segments.join('/');
505
+ }
506
+ }
507
+ /**
508
+ * Common path patterns as constants for frequently used paths
509
+ */
510
+ const PATH_PATTERNS = {
511
+ /**
512
+ * Base pattern for all SeaVerse data
513
+ */
514
+ APP_DATA: 'appData',
515
+ /**
516
+ * Permission layers
517
+ */
518
+ PUBLIC_READ: 'publicRead',
519
+ PUBLIC_DATA: 'publicData',
520
+ USER_DATA: 'userData',
521
+ };
522
+
523
+ /**
524
+ * Firestore Helper - LLM-Friendly Firestore Operations
525
+ *
526
+ * This helper class automatically handles required fields (_appId, _createdAt, _createdBy)
527
+ * so LLM doesn't need to remember them. It provides a simple, high-level API.
528
+ *
529
+ * 🎯 LLM-FIRST DESIGN:
530
+ * - No need to remember required fields
531
+ * - No need to construct paths manually
532
+ * - No need to import serverTimestamp
533
+ * - One method does everything
534
+ */
535
+ /**
536
+ * Firestore operations helper with automatic metadata injection
537
+ *
538
+ * This class wraps Firestore operations and automatically adds required fields:
539
+ * - _appId: Application ID (for data isolation)
540
+ * - _createdAt: Server timestamp (for creation time)
541
+ * - _createdBy: User ID (for ownership tracking)
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const helper = new FirestoreHelper(db, appId, userId);
546
+ *
547
+ * // ✅ LLM-friendly: Just pass your data, required fields auto-injected
548
+ * await helper.addToPublicData('posts', {
549
+ * title: 'My Post',
550
+ * content: 'Hello world'
551
+ * });
552
+ *
553
+ * // ❌ OLD WAY: LLM needs to remember 3 required fields
554
+ * await addDoc(collection(db, path), {
555
+ * _appId: appId,
556
+ * _createdAt: serverTimestamp(),
557
+ * _createdBy: userId,
558
+ * title: 'My Post',
559
+ * content: 'Hello world'
560
+ * });
561
+ * ```
562
+ */
563
+ class FirestoreHelper {
564
+ constructor(db, appId, userId) {
565
+ this.db = db;
566
+ this.appId = appId;
567
+ this.userId = userId;
568
+ }
569
+ /**
570
+ * Add document to publicData (shared, read/write for all users)
571
+ *
572
+ * Automatically injects: _appId, _createdAt, _createdBy
573
+ *
574
+ * @param collectionName - Collection name (e.g., 'posts', 'comments')
575
+ * @param data - Your business data
576
+ * @returns Document reference
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * // ✅ LLM-friendly: Simple and clean
581
+ * const docRef = await helper.addToPublicData('posts', {
582
+ * title: 'My First Post',
583
+ * content: 'Hello world'
584
+ * });
585
+ * console.log('Created post:', docRef.id);
586
+ * ```
587
+ */
588
+ async addToPublicData(collectionName, data) {
589
+ const path = getPublicDataPath(this.appId, collectionName);
590
+ return this.addDocWithMeta(path, data);
591
+ }
592
+ /**
593
+ * Add document to userData (private, only owner can read/write)
594
+ *
595
+ * Automatically injects: _appId, _createdAt, _createdBy
596
+ *
597
+ * @param collectionName - Collection name (e.g., 'notes', 'settings')
598
+ * @param data - Your business data
599
+ * @returns Document reference
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * // ✅ LLM-friendly: Private data, auto-isolated
604
+ * await helper.addToUserData('notes', {
605
+ * title: 'Private Note',
606
+ * content: 'Only I can see this'
607
+ * });
608
+ * ```
609
+ */
610
+ async addToUserData(collectionName, data) {
611
+ const path = getUserDataPath(this.appId, this.userId, collectionName);
612
+ return this.addDocWithMeta(path, data);
613
+ }
614
+ /**
615
+ * Get all documents from publicData collection
616
+ *
617
+ * @param collectionName - Collection name
618
+ * @returns QuerySnapshot with documents
619
+ *
620
+ * @example
621
+ * ```typescript
622
+ * const snapshot = await helper.getPublicData('posts');
623
+ * snapshot.forEach(doc => {
624
+ * console.log(doc.id, doc.data());
625
+ * });
626
+ * ```
627
+ */
628
+ async getPublicData(collectionName) {
629
+ const path = getPublicDataPath(this.appId, collectionName);
630
+ return this.getDocs(path);
631
+ }
632
+ /**
633
+ * Get all documents from userData collection (user's private data)
634
+ *
635
+ * @param collectionName - Collection name
636
+ * @returns QuerySnapshot with documents
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * const snapshot = await helper.getUserData('notes');
641
+ * snapshot.forEach(doc => {
642
+ * console.log('My note:', doc.data());
643
+ * });
644
+ * ```
645
+ */
646
+ async getUserData(collectionName) {
647
+ const path = getUserDataPath(this.appId, this.userId, collectionName);
648
+ return this.getDocs(path);
649
+ }
650
+ /**
651
+ * Get all documents from publicRead collection (read-only for users)
652
+ *
653
+ * @param collectionName - Collection name
654
+ * @returns QuerySnapshot with documents
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * const configs = await helper.getPublicRead('config');
659
+ * ```
660
+ */
661
+ async getPublicRead(collectionName) {
662
+ const path = getPublicReadPath(this.appId, collectionName);
663
+ return this.getDocs(path);
664
+ }
665
+ /**
666
+ * Get collection reference for publicData
667
+ * Use this for advanced queries with where(), orderBy(), limit()
668
+ *
669
+ * @param collectionName - Collection name
670
+ * @returns Collection reference
671
+ *
672
+ * @example
673
+ * ```typescript
674
+ * import { query, where, orderBy } from 'firebase/firestore';
675
+ *
676
+ * const postsRef = helper.publicDataCollection('posts');
677
+ * const q = query(
678
+ * postsRef,
679
+ * where('_createdBy', '==', userId),
680
+ * orderBy('_createdAt', 'desc')
681
+ * );
682
+ * const snapshot = await getDocs(q);
683
+ * ```
684
+ */
685
+ publicDataCollection(collectionName) {
686
+ const path = getPublicDataPath(this.appId, collectionName);
687
+ return this.getCollection(path);
688
+ }
689
+ /**
690
+ * Get collection reference for userData
691
+ *
692
+ * @param collectionName - Collection name
693
+ * @returns Collection reference
694
+ */
695
+ userDataCollection(collectionName) {
696
+ const path = getUserDataPath(this.appId, this.userId, collectionName);
697
+ return this.getCollection(path);
698
+ }
699
+ /**
700
+ * Get collection reference for publicRead
701
+ *
702
+ * @param collectionName - Collection name
703
+ * @returns Collection reference
704
+ */
705
+ publicReadCollection(collectionName) {
706
+ const path = getPublicReadPath(this.appId, collectionName);
707
+ return this.getCollection(path);
708
+ }
709
+ /**
710
+ * Update document with automatic metadata update
711
+ *
712
+ * Automatically sets: _updatedAt, _updatedBy
713
+ *
714
+ * @param collectionPath - Full collection path
715
+ * @param docId - Document ID
716
+ * @param data - Data to update
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * await helper.updateDoc(
721
+ * getPublicDataPath(appId, 'posts'),
722
+ * 'post-123',
723
+ * { title: 'Updated Title' }
724
+ * );
725
+ * ```
726
+ */
727
+ async updateDoc(collectionPath, docId, data) {
728
+ const { updateDoc, doc, serverTimestamp } = await this.loadFirestore();
729
+ const docRef = doc(this.db, collectionPath, docId);
730
+ return updateDoc(docRef, {
731
+ ...data,
732
+ _updatedAt: serverTimestamp(),
733
+ _updatedBy: this.userId
734
+ });
735
+ }
736
+ /**
737
+ * Delete document
738
+ *
739
+ * @param collectionPath - Full collection path
740
+ * @param docId - Document ID
741
+ */
742
+ async deleteDoc(collectionPath, docId) {
743
+ const { deleteDoc, doc } = await this.loadFirestore();
744
+ const docRef = doc(this.db, collectionPath, docId);
745
+ return deleteDoc(docRef);
746
+ }
747
+ /**
748
+ * Get single document by ID
749
+ *
750
+ * @param collectionPath - Full collection path
751
+ * @param docId - Document ID
752
+ *
753
+ * @example
754
+ * ```typescript
755
+ * const docSnap = await helper.getDoc(
756
+ * getPublicDataPath(appId, 'posts'),
757
+ * 'post-123'
758
+ * );
759
+ * if (docSnap.exists()) {
760
+ * console.log(docSnap.data());
761
+ * }
762
+ * ```
763
+ */
764
+ async getDoc(collectionPath, docId) {
765
+ const { getDoc, doc } = await this.loadFirestore();
766
+ const docRef = doc(this.db, collectionPath, docId);
767
+ return getDoc(docRef);
768
+ }
769
+ // ============================================================================
770
+ // Private Helper Methods
771
+ // ============================================================================
772
+ /**
773
+ * Internal: Add document with metadata injection
774
+ */
775
+ async addDocWithMeta(collectionPath, data) {
776
+ const { addDoc, collection, serverTimestamp } = await this.loadFirestore();
777
+ const colRef = collection(this.db, collectionPath);
778
+ return addDoc(colRef, {
779
+ _appId: this.appId,
780
+ _createdAt: serverTimestamp(),
781
+ _createdBy: this.userId,
782
+ ...data
783
+ });
784
+ }
785
+ /**
786
+ * Internal: Get all documents from collection
787
+ */
788
+ async getDocs(collectionPath) {
789
+ const { getDocs, collection } = await this.loadFirestore();
790
+ const colRef = collection(this.db, collectionPath);
791
+ return getDocs(colRef);
792
+ }
793
+ /**
794
+ * Internal: Get collection reference
795
+ */
796
+ getCollection(collectionPath) {
797
+ // Note: This is sync, so we can't use dynamic import
798
+ // We assume firebase/firestore is already loaded by initializeWithToken
799
+ const { collection } = require('firebase/firestore');
800
+ return collection(this.db, collectionPath);
801
+ }
802
+ /**
803
+ * Internal: Lazy load Firebase Firestore functions
804
+ */
805
+ async loadFirestore() {
806
+ const firestore = await import('firebase/firestore');
807
+ return {
808
+ collection: firestore.collection,
809
+ doc: firestore.doc,
810
+ addDoc: firestore.addDoc,
811
+ setDoc: firestore.setDoc,
812
+ getDoc: firestore.getDoc,
813
+ getDocs: firestore.getDocs,
814
+ updateDoc: firestore.updateDoc,
815
+ deleteDoc: firestore.deleteDoc,
816
+ serverTimestamp: firestore.serverTimestamp,
817
+ query: firestore.query,
818
+ where: firestore.where,
819
+ orderBy: firestore.orderBy,
820
+ limit: firestore.limit
821
+ };
822
+ }
823
+ }
824
+ /**
825
+ * Standalone helper function: Add document with automatic metadata injection
826
+ *
827
+ * Use this if you don't want to create a FirestoreHelper instance.
828
+ *
829
+ * @param db - Firestore instance
830
+ * @param collectionPath - Full collection path
831
+ * @param appId - Application ID
832
+ * @param userId - User ID
833
+ * @param data - Your business data
834
+ * @returns Document reference
835
+ *
836
+ * @example
837
+ * ```typescript
838
+ * import { addDocWithMeta, getPublicDataPath } from '@seaverse/data-service-sdk';
839
+ *
840
+ * const docRef = await addDocWithMeta(
841
+ * db,
842
+ * getPublicDataPath(appId, 'posts'),
843
+ * appId,
844
+ * userId,
845
+ * { title: 'Post', content: 'Hello' }
846
+ * );
847
+ * ```
848
+ */
849
+ async function addDocWithMeta(db, collectionPath, appId, userId, data) {
850
+ const { addDoc, collection, serverTimestamp } = await import('firebase/firestore');
851
+ const colRef = collection(db, collectionPath);
852
+ return addDoc(colRef, {
853
+ _appId: appId,
854
+ _createdAt: serverTimestamp(),
855
+ _createdBy: userId,
856
+ ...data
857
+ });
858
+ }
859
+ /**
860
+ * Standalone helper function: Update document with automatic metadata
861
+ *
862
+ * @param db - Firestore instance
863
+ * @param collectionPath - Full collection path
864
+ * @param docId - Document ID
865
+ * @param userId - User ID (for _updatedBy field)
866
+ * @param data - Data to update
867
+ *
868
+ * @example
869
+ * ```typescript
870
+ * import { updateDocWithMeta, getPublicDataPath } from '@seaverse/data-service-sdk';
871
+ *
872
+ * await updateDocWithMeta(
873
+ * db,
874
+ * getPublicDataPath(appId, 'posts'),
875
+ * 'post-123',
876
+ * userId,
877
+ * { title: 'Updated Title' }
878
+ * );
879
+ * ```
880
+ */
881
+ async function updateDocWithMeta(db, collectionPath, docId, userId, data) {
882
+ const { updateDoc, doc, serverTimestamp } = await import('firebase/firestore');
883
+ const docRef = doc(db, collectionPath, docId);
884
+ return updateDoc(docRef, {
885
+ ...data,
886
+ _updatedAt: serverTimestamp(),
887
+ _updatedBy: userId
888
+ });
889
+ }
890
+
268
891
  /**
269
892
  * Create Firebase configuration from Firestore token response
270
893
  *
@@ -298,23 +921,32 @@ function getFirebaseConfig(tokenResponse) {
298
921
  * 1. Creates Firebase config from token response
299
922
  * 2. Initializes Firebase app
300
923
  * 3. Signs in with the custom token
301
- * 4. Returns authenticated Firebase instances
924
+ * 4. Creates FirestoreHelper for LLM-friendly operations
925
+ * 5. Returns authenticated Firebase instances
302
926
  *
303
927
  * IMPORTANT: This function requires Firebase SDK to be installed separately:
304
928
  * npm install firebase
305
929
  *
930
+ * 🎯 LLM RECOMMENDED: Use the `helper` object for simplified operations
931
+ *
306
932
  * @param tokenResponse - The Firestore token response from SDK
307
- * @returns Object containing initialized Firebase app, auth, and db instances
933
+ * @returns Object containing initialized Firebase instances and LLM-friendly helper
308
934
  *
309
935
  * @example
310
936
  * ```typescript
311
937
  * import { initializeWithToken } from '@seaverse/data-service-sdk';
312
938
  *
313
939
  * const tokenResponse = await client.generateGuestFirestoreToken({ app_id: 'my-app' });
314
- * const { app, auth, db, userId, appId } = await initializeWithToken(tokenResponse);
940
+ * const { db, userId, appId, helper } = await initializeWithToken(tokenResponse);
315
941
  *
316
- * // Ready to use Firestore!
317
- * const snapshot = await getDocs(collection(db, `appData/${appId}/publicData/posts`));
942
+ * // LLM-FRIENDLY: Use helper (automatic required fields)
943
+ * await helper.addToPublicData('posts', {
944
+ * title: 'My Post',
945
+ * content: 'Hello'
946
+ * });
947
+ *
948
+ * // ❌ OLD WAY: Manual (need to remember required fields)
949
+ * // await addDoc(collection(db, path), { _appId, _createdAt, _createdBy, ...data });
318
950
  * ```
319
951
  */
320
952
  async function initializeWithToken(tokenResponse) {
@@ -346,19 +978,143 @@ async function initializeWithToken(tokenResponse) {
346
978
  // Get Firestore instance with correct database ID
347
979
  // IMPORTANT: Must specify database_id, not just use default!
348
980
  const db = getFirestore(app, tokenResponse.database_id);
981
+ const userId = tokenResponse.user_id;
982
+ const appId = tokenResponse.app_id || '';
983
+ // Create LLM-friendly helper
984
+ const helper = new FirestoreHelper(db, appId, userId);
349
985
  return {
350
986
  app,
351
987
  auth,
352
988
  db,
353
- userId: tokenResponse.user_id,
354
- appId: tokenResponse.app_id || '',
989
+ userId,
990
+ appId,
991
+ helper,
355
992
  };
356
993
  }
357
994
 
995
+ Object.defineProperty(exports, "addDoc", {
996
+ enumerable: true,
997
+ get: function () { return firestore.addDoc; }
998
+ });
999
+ Object.defineProperty(exports, "arrayRemove", {
1000
+ enumerable: true,
1001
+ get: function () { return firestore.arrayRemove; }
1002
+ });
1003
+ Object.defineProperty(exports, "arrayUnion", {
1004
+ enumerable: true,
1005
+ get: function () { return firestore.arrayUnion; }
1006
+ });
1007
+ Object.defineProperty(exports, "collection", {
1008
+ enumerable: true,
1009
+ get: function () { return firestore.collection; }
1010
+ });
1011
+ Object.defineProperty(exports, "collectionGroup", {
1012
+ enumerable: true,
1013
+ get: function () { return firestore.collectionGroup; }
1014
+ });
1015
+ Object.defineProperty(exports, "deleteDoc", {
1016
+ enumerable: true,
1017
+ get: function () { return firestore.deleteDoc; }
1018
+ });
1019
+ Object.defineProperty(exports, "deleteField", {
1020
+ enumerable: true,
1021
+ get: function () { return firestore.deleteField; }
1022
+ });
1023
+ Object.defineProperty(exports, "doc", {
1024
+ enumerable: true,
1025
+ get: function () { return firestore.doc; }
1026
+ });
1027
+ Object.defineProperty(exports, "endAt", {
1028
+ enumerable: true,
1029
+ get: function () { return firestore.endAt; }
1030
+ });
1031
+ Object.defineProperty(exports, "endBefore", {
1032
+ enumerable: true,
1033
+ get: function () { return firestore.endBefore; }
1034
+ });
1035
+ Object.defineProperty(exports, "getDoc", {
1036
+ enumerable: true,
1037
+ get: function () { return firestore.getDoc; }
1038
+ });
1039
+ Object.defineProperty(exports, "getDocs", {
1040
+ enumerable: true,
1041
+ get: function () { return firestore.getDocs; }
1042
+ });
1043
+ Object.defineProperty(exports, "getFirestore", {
1044
+ enumerable: true,
1045
+ get: function () { return firestore.getFirestore; }
1046
+ });
1047
+ Object.defineProperty(exports, "increment", {
1048
+ enumerable: true,
1049
+ get: function () { return firestore.increment; }
1050
+ });
1051
+ Object.defineProperty(exports, "limit", {
1052
+ enumerable: true,
1053
+ get: function () { return firestore.limit; }
1054
+ });
1055
+ Object.defineProperty(exports, "limitToLast", {
1056
+ enumerable: true,
1057
+ get: function () { return firestore.limitToLast; }
1058
+ });
1059
+ Object.defineProperty(exports, "onSnapshot", {
1060
+ enumerable: true,
1061
+ get: function () { return firestore.onSnapshot; }
1062
+ });
1063
+ Object.defineProperty(exports, "orderBy", {
1064
+ enumerable: true,
1065
+ get: function () { return firestore.orderBy; }
1066
+ });
1067
+ Object.defineProperty(exports, "query", {
1068
+ enumerable: true,
1069
+ get: function () { return firestore.query; }
1070
+ });
1071
+ Object.defineProperty(exports, "runTransaction", {
1072
+ enumerable: true,
1073
+ get: function () { return firestore.runTransaction; }
1074
+ });
1075
+ Object.defineProperty(exports, "serverTimestamp", {
1076
+ enumerable: true,
1077
+ get: function () { return firestore.serverTimestamp; }
1078
+ });
1079
+ Object.defineProperty(exports, "setDoc", {
1080
+ enumerable: true,
1081
+ get: function () { return firestore.setDoc; }
1082
+ });
1083
+ Object.defineProperty(exports, "startAfter", {
1084
+ enumerable: true,
1085
+ get: function () { return firestore.startAfter; }
1086
+ });
1087
+ Object.defineProperty(exports, "startAt", {
1088
+ enumerable: true,
1089
+ get: function () { return firestore.startAt; }
1090
+ });
1091
+ Object.defineProperty(exports, "updateDoc", {
1092
+ enumerable: true,
1093
+ get: function () { return firestore.updateDoc; }
1094
+ });
1095
+ Object.defineProperty(exports, "where", {
1096
+ enumerable: true,
1097
+ get: function () { return firestore.where; }
1098
+ });
1099
+ Object.defineProperty(exports, "writeBatch", {
1100
+ enumerable: true,
1101
+ get: function () { return firestore.writeBatch; }
1102
+ });
358
1103
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
359
1104
  exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
360
1105
  exports.DataServiceClient = DataServiceClient;
361
1106
  exports.ENDPOINTS = ENDPOINTS;
1107
+ exports.FirestoreHelper = FirestoreHelper;
1108
+ exports.PATH_PATTERNS = PATH_PATTERNS;
1109
+ exports.PathBuilder = PathBuilder;
1110
+ exports.addDocWithMeta = addDocWithMeta;
362
1111
  exports.getFirebaseConfig = getFirebaseConfig;
1112
+ exports.getPublicDataDocPath = getPublicDataDocPath;
1113
+ exports.getPublicDataPath = getPublicDataPath;
1114
+ exports.getPublicReadDocPath = getPublicReadDocPath;
1115
+ exports.getPublicReadPath = getPublicReadPath;
1116
+ exports.getUserDataDocPath = getUserDataDocPath;
1117
+ exports.getUserDataPath = getUserDataPath;
363
1118
  exports.initializeWithToken = initializeWithToken;
1119
+ exports.updateDocWithMeta = updateDocWithMeta;
364
1120
  //# sourceMappingURL=index.cjs.map