Advertisement
Froredion

firestoreCompat.ts

Apr 21st, 2025
868
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Firestore Compatibility Utility
  3.  *
  4.  * This module provides a universal interface for working with Firestore across different platforms
  5.  * (web and React Native). It abstracts away the differences between the Firebase Web SDK and
  6.  * React Native Firebase SDK, providing consistent function signatures and behavior.
  7.  *
  8.  * Usage:
  9.  * - Import firestoreDbInstance as firestoreDb
  10.  * - Import firestoreFunctionsCompat as firestoreFunctions
  11.  *
  12.  * This allows for platform-agnostic Firestore operations throughout the application.
  13.  */
  14.  
  15. import { Platform } from 'react-native';
  16. import { db } from '../../config';
  17. import { useWebSDK, logPlatformDetection } from '@/lib/utils/platform';
  18. import structuredClone from '@ungap/structured-clone';
  19.  
  20. // Environment detection
  21. const isWebSDKFlag = useWebSDK();
  22.  
  23. logPlatformDetection('FirestoreCompat');
  24.  
  25. // Define constraint types for React Native Firebase
  26. interface QueryConstraint {
  27.   type: 'where' | 'orderBy' | 'limit' | 'startAfter' | 'startAt' | 'endBefore' | 'endAt';
  28.   args: any[];
  29. }
  30.  
  31. // Define the Firestore functions interface
  32. export interface FirestoreFunctions {
  33.   doc: (db: any, path: string, ...segments: string[]) => any;
  34.   setDoc: (ref: any, data: any, options?: any) => Promise<any>;
  35.   getDoc: (ref: any) => Promise<any>;
  36.   collection: (db: any, path: string, ...segments: string[]) => any;
  37.   addDoc: (ref: any, data: any) => Promise<any>;
  38.   serverTimestamp: () => any;
  39.   query: (ref: any, ...queryConstraints: any[]) => any;
  40.   where: (field: string, opStr: string, value: any) => any;
  41.   orderBy: (field: string, direction?: 'asc' | 'desc') => any;
  42.   limit: (limit: number) => any;
  43.   startAfter: (value: any) => any;
  44.   startAt: (value: any) => any;
  45.   endBefore: (value: any) => any;
  46.   endAt: (value: any) => any;
  47.   getDocs: (queryRef: any) => Promise<any>;
  48.   onSnapshot: (ref: any, onNext: (snapshot: any) => void, onError?: (error: Error) => void) => () => void;
  49.   updateDoc: (ref: any, data: any) => Promise<any>;
  50.   arrayUnion: (...values: any[]) => any;
  51.   arrayRemove: (...values: any[]) => any;
  52.   deleteDoc: (ref: any) => Promise<any>;
  53.   // Batch operations
  54.   writeBatch: (db: any) => any;
  55.   batchSetDoc: (batch: any, ref: any, data: any, options?: any) => void;
  56.   batchUpdateDoc: (batch: any, ref: any, data: any) => void;
  57.   batchDeleteDoc: (batch: any, ref: any) => void;
  58.   batchCommit: (batch: any) => Promise<void>;
  59. }
  60.  
  61. // Initialize Firestore instance and functions based on platform
  62. let firestoreDbInstance: any;
  63. let firestoreFunctionsCompat: FirestoreFunctions;
  64.  
  65. // Add at the top for consistent logging
  66. const logSnapshot = (...args: any[]) => {
  67.   if (process.env.NODE_ENV !== 'production') {
  68.     // console.log('[FirestoreCompat][Snapshot]', ...args);
  69.   }
  70. };
  71.  
  72. /**
  73.  * Creates a Web SDK compatible DocumentSnapshot wrapper
  74.  * Ensures all methods and properties match the Firebase JS SDK behavior
  75.  */
  76. const createWebSDKCompatibleSnapshot = (snapshot: any, docRef: any): any => {
  77.   logSnapshot('createWebSDKCompatibleSnapshot: called', { hasSnapshot: !!snapshot, docRef });
  78.   // Handle null or undefined snapshots
  79.   if (!snapshot) {
  80.     // If this error happens:
  81.     // 1. [firestore/permission-denied] likely means firestore.rules permission is not set correctly
  82.     logSnapshot('createWebSDKCompatibleSnapshot: Received null or undefined document snapshot', { docRef });
  83.     console.error('Error: Received null or undefined document snapshot');
  84.    
  85.     // Return a valid but empty document snapshot that clearly indicates an error occurred
  86.     const emptySnapshot = {
  87.       exists: () => false,
  88.       data: () => null,
  89.       id: docRef?.id || 'unknown-doc-id',
  90.       ref: docRef,
  91.       get: () => undefined,
  92.       // Flags to identify this as a wrapped error snapshot
  93.       _wrapped: true,
  94.       _type: 'document',
  95.       _isError: true,
  96.       _errorType: 'null_snapshot'
  97.     };
  98.     logSnapshot('createWebSDKCompatibleSnapshot: returning empty snapshot', emptySnapshot);
  99.     return emptySnapshot;
  100.   }
  101.   // Handle case when we receive an already wrapped snapshot
  102.   if (snapshot._wrapped && snapshot._type === 'document') {
  103.     logSnapshot('createWebSDKCompatibleSnapshot: already wrapped snapshot', snapshot);
  104.     return snapshot;
  105.   }
  106.   // Determine if the document exists
  107.   let existsValue = false;
  108.   if (typeof snapshot.exists === 'function') {
  109.     existsValue = snapshot.exists();
  110.   } else if (typeof snapshot.exists === 'boolean') {
  111.     existsValue = snapshot.exists;
  112.   } else {
  113.     try {
  114.       const data = typeof snapshot.data === 'function' ? snapshot.data() : null;
  115.       existsValue = !!data;
  116.     } catch (error) {
  117.       existsValue = false;
  118.       logSnapshot('createWebSDKCompatibleSnapshot: error checking existence', error);
  119.     }
  120.   }
  121.   // Build the snapshot data once to avoid repeated calls
  122.   let snapshotData = null;
  123.   try {
  124.     snapshotData = typeof snapshot.data === 'function' ? snapshot.data() : null;
  125.   } catch (error) {
  126.     snapshotData = null;
  127.     logSnapshot('createWebSDKCompatibleSnapshot: error getting data', error);
  128.   }
  129.   const wrappedSnapshot = {
  130.     data: function() {
  131.       logSnapshot('wrappedSnapshot.data() called', { snapshotData });
  132.       return snapshotData;
  133.     },
  134.     exists: function() {
  135.       logSnapshot('wrappedSnapshot.exists() called', { existsValue });
  136.       return existsValue;
  137.     },
  138.     id: snapshot.id || docRef?.id || '',
  139.     ref: docRef,
  140.     get: function(fieldPath: string) {
  141.       logSnapshot('wrappedSnapshot.get() called', { fieldPath });
  142.       if (!snapshotData) return undefined;
  143.       if (fieldPath.includes('.')) {
  144.         const parts = fieldPath.split('.');
  145.         let value = snapshotData;
  146.         for (const part of parts) {
  147.           if (value === undefined || value === null) return undefined;
  148.           value = value[part];
  149.         }
  150.         logSnapshot('wrappedSnapshot.get() dot notation result', { fieldPath, value });
  151.         return value;
  152.       }
  153.       logSnapshot('wrappedSnapshot.get() direct result', { fieldPath, value: snapshotData[fieldPath] });
  154.       return snapshotData[fieldPath];
  155.     },
  156.     _wrapped: true,
  157.     _type: 'document',
  158.     _originalSnapshot: snapshot
  159.   };
  160.   logSnapshot('createWebSDKCompatibleSnapshot: returning wrappedSnapshot', wrappedSnapshot);
  161.   return wrappedSnapshot;
  162. };
  163.  
  164. /**
  165.  * Creates a Web SDK compatible QuerySnapshot wrapper
  166.  * Ensures all methods and properties match the Firebase JS SDK behavior
  167.  */
  168. const createWebSDKCompatibleQuerySnapshot = (snapshot: any): any => {
  169.   logSnapshot('createWebSDKCompatibleQuerySnapshot: called', { hasSnapshot: !!snapshot, docsCount: Array.isArray(snapshot?.docs) ? snapshot.docs.length : 0 });
  170.   if (!snapshot) {
  171.     logSnapshot('createWebSDKCompatibleQuerySnapshot: Received null or undefined query snapshot');
  172.     return {
  173.       docs: [],
  174.       empty: true,
  175.       size: 0,
  176.       forEach: (callback: any) => {},
  177.       docChanges: () => [],
  178.       _wrapped: true,
  179.       _type: 'query',
  180.       _isError: true,
  181.       _errorType: 'null_snapshot'
  182.     };
  183.   }
  184.   if (snapshot._wrapped && snapshot._type === 'query') {
  185.     logSnapshot('createWebSDKCompatibleQuerySnapshot: already wrapped snapshot', { docsCount: Array.isArray(snapshot?.docs) ? snapshot.docs.length : 0 });
  186.     return snapshot;
  187.   }
  188.   const docsArray = Array.isArray(snapshot.docs)
  189.     ? snapshot.docs.map((doc: any) => {
  190.         if (doc._wrapped && doc._type === 'document') {
  191.           return doc;
  192.         }
  193.         return createWebSDKCompatibleSnapshot(doc, doc.ref);
  194.       })
  195.     : [];
  196.   const wrappedSnapshot = {
  197.     docs: docsArray,
  198.     empty: docsArray.length === 0,
  199.     size: docsArray.length,
  200.     forEach: function(callback: (doc: any) => void) {
  201.       logSnapshot('wrappedQuerySnapshot.forEach() called', { docsCount: docsArray.length });
  202.       docsArray.forEach((doc: any) => callback(doc));
  203.     },
  204.     docChanges: function() {
  205.       logSnapshot('wrappedQuerySnapshot.docChanges() called', { docsCount: docsArray.length });
  206.       if (typeof snapshot.docChanges === 'function') {
  207.         return snapshot.docChanges().map((change: any) => ({
  208.           ...change,
  209.           doc: change.doc._wrapped
  210.             ? change.doc
  211.             : createWebSDKCompatibleSnapshot(change.doc, change.doc.ref)
  212.         }));
  213.       }
  214.       return docsArray.map((doc: any, index: number) => ({
  215.         type: 'added',
  216.         doc,
  217.         oldIndex: -1,
  218.         newIndex: index
  219.       }));
  220.     },
  221.     _wrapped: true,
  222.     _type: 'query',
  223.     _originalSnapshot: snapshot
  224.   };
  225.   logSnapshot('createWebSDKCompatibleQuerySnapshot: returning wrappedSnapshot', { docsCount: docsArray.length });
  226.   return wrappedSnapshot;
  227. };
  228.  
  229. /**
  230.  * Creates a special wrapper for document snapshots that need to behave like query snapshots
  231.  * This bridges the gap for code that expects a QuerySnapshot but receives a DocumentSnapshot
  232.  */
  233. const createDocumentAsQuerySnapshot = (docSnapshot: any): any => {
  234.   logSnapshot('createDocumentAsQuerySnapshot: called', { hasDocSnapshot: !!docSnapshot });
  235.   if (!docSnapshot) {
  236.     logSnapshot('createDocumentAsQuerySnapshot: null input');
  237.     return createWebSDKCompatibleQuerySnapshot(null);
  238.   }
  239.   if (docSnapshot._wrapped && docSnapshot._type === 'query') {
  240.     logSnapshot('createDocumentAsQuerySnapshot: already wrapped query snapshot', docSnapshot);
  241.     return docSnapshot;
  242.   }
  243.   const wrappedDoc = docSnapshot._wrapped && docSnapshot._type === 'document'
  244.     ? docSnapshot
  245.     : createWebSDKCompatibleSnapshot(docSnapshot, docSnapshot.ref);
  246.   const querySnapshot = {
  247.     docs: wrappedDoc.exists() ? [wrappedDoc] : [],
  248.     empty: !wrappedDoc.exists(),
  249.     size: wrappedDoc.exists() ? 1 : 0,
  250.     forEach: function(callback: (doc: any) => void) {
  251.       logSnapshot('syntheticQuerySnapshot.forEach() called', { exists: wrappedDoc.exists() });
  252.       if (wrappedDoc.exists()) {
  253.         callback(wrappedDoc);
  254.       }
  255.     },
  256.     docChanges: function() {
  257.       logSnapshot('syntheticQuerySnapshot.docChanges() called', { exists: wrappedDoc.exists() });
  258.       if (!wrappedDoc.exists()) return [];
  259.       return [{
  260.         type: 'added',
  261.         doc: wrappedDoc,
  262.         oldIndex: -1,
  263.         newIndex: 0
  264.       }];
  265.     },
  266.     _wrapped: true,
  267.     _type: 'query',
  268.     _isSynthetic: true,
  269.     _wrappedDoc: wrappedDoc
  270.   };
  271.   logSnapshot('createDocumentAsQuerySnapshot: returning synthetic querySnapshot', querySnapshot);
  272.   return querySnapshot;
  273. };
  274.  
  275. /**
  276.  * Enhanced wrapper function for onSnapshot that properly handles all reference types
  277.  * and ensures consistent behavior between Web SDK and React Native Firebase
  278.  *
  279.  * @param ref The Firestore reference (document, collection, or query)
  280.  * @param onNext Callback function to be called when the snapshot changes
  281.  * @param onError Optional callback function to be called when an error occurs
  282.  * @returns A function to unsubscribe from the snapshot listener
  283.  */
  284. const wrappedOnSnapshot = (ref: any, onNext: (snapshot: any) => void, onError?: (error: Error) => void): (() => void) => {
  285.   logSnapshot('wrappedOnSnapshot: called', { hasRef: !!ref });
  286.   if (!ref) {
  287.     logSnapshot('wrappedOnSnapshot: Received null or undefined reference');
  288.     console.error('wrappedOnSnapshot: Received null or undefined reference');
  289.     return () => {};
  290.   }
  291.  
  292.   // Use detectFirestoreRefType for reference type detection
  293.   const refType = detectFirestoreRefType(ref);
  294.  
  295.   // Determine path for special case handling
  296.   let refPath = '';
  297.   try {
  298.     refPath = ref.path || '';
  299.   } catch (e) {
  300.     // Path might not be accessible for all reference types
  301.   }
  302.  
  303.   // Get the appropriate platform-specific onSnapshot implementation
  304.   const platformOnSnapshot = (ref: any, onNext: any, onError?: any) => ref.onSnapshot(onNext, onError);
  305.  
  306.   // Return the unsubscribe function
  307.   return platformOnSnapshot(ref, (snapshot: any) => {
  308.     logSnapshot('wrappedOnSnapshot: snapshot received', { docsCount: Array.isArray(snapshot?.docs) ? snapshot.docs.length : undefined });
  309.     try {
  310.       if (refType === 'document') {
  311.         logSnapshot('wrappedOnSnapshot: Detected Document Reference');
  312.         const wrappedSnapshot = createWebSDKCompatibleSnapshot(snapshot, ref);
  313.         logSnapshot('wrappedOnSnapshot: calling onNext with wrappedSnapshot', wrappedSnapshot);
  314.         onNext(wrappedSnapshot);
  315.       }
  316.       else if (refType === 'collection' || refType === 'query') {
  317.         logSnapshot('wrappedOnSnapshot: Detected Collection/Query Reference');
  318.         const wrappedSnapshot = createWebSDKCompatibleQuerySnapshot(snapshot);
  319.         onNext(wrappedSnapshot);
  320.       }
  321.       else {
  322.         logSnapshot('wrappedOnSnapshot: Unknown Reference Type');
  323.         if (snapshot && typeof snapshot.docs !== 'undefined') {
  324.           logSnapshot('wrappedOnSnapshot: Fallback as QuerySnapshot', { docsCount: Array.isArray(snapshot?.docs) ? snapshot.docs.length : 0 });
  325.           const wrappedSnapshot = createWebSDKCompatibleQuerySnapshot(snapshot);
  326.           onNext(wrappedSnapshot);
  327.         }
  328.         else if (snapshot && (typeof snapshot.exists === 'function' || typeof snapshot.exists === 'boolean')) {
  329.           logSnapshot('wrappedOnSnapshot: Fallback as DocumentSnapshot');
  330.           const wrappedSnapshot = createWebSDKCompatibleSnapshot(snapshot, ref);
  331.           const callStack = new Error().stack || '';
  332.           if (callStack.includes('forEach')) {
  333.             logSnapshot('wrappedOnSnapshot: callStack includes forEach, using createDocumentAsQuerySnapshot');
  334.             onNext(createDocumentAsQuerySnapshot(wrappedSnapshot));
  335.           } else {
  336.             onNext(wrappedSnapshot);
  337.           }
  338.         }
  339.         else {
  340.           logSnapshot('wrappedOnSnapshot: Last resort, passing through raw snapshot');
  341.           console.warn('wrappedOnSnapshot: Unknown snapshot type, passing through raw snapshot');
  342.           onNext(snapshot);
  343.         }
  344.       }
  345.     } catch (error) {
  346.       console.error('Error in wrappedOnSnapshot:', error);
  347.     }
  348.   }, onError);
  349. };
  350.  
  351. /**
  352.  * Detects the type of Firestore reference: document, collection, query, or unknown.
  353.  * Compatible with both Web SDK and React Native Firebase.
  354.  */
  355. export function detectFirestoreRefType(ref: any): 'document' | 'collection' | 'query' | 'unknown' {
  356.   // Web SDK: DocumentReference has .id and .parent, but no .where()
  357.   if (ref?.id && ref?.parent && typeof ref?.where !== 'function') {
  358.     return 'document';
  359.   }
  360.  
  361.   // Web SDK: Query has .where() and .get() but no .id or .parent
  362.   if (typeof ref?.where === 'function' && typeof ref?.get === 'function') {
  363.     return 'query';
  364.   }
  365.  
  366.   // RNFB (React Native Firebase): detect collection/document based on _parts
  367.   if (Array.isArray(ref?._parts)) {
  368.     const partsLength = ref._parts.length;
  369.     if (partsLength % 2 === 0) {
  370.       return 'document'; // Even number of parts = document (e.g., /col/doc)
  371.     } else {
  372.       return 'collection'; // Odd number of parts = collection (e.g., /col)
  373.     }
  374.   }
  375.  
  376.   return 'unknown';
  377. }
  378.  
  379. if (isWebSDKFlag) {
  380.   // Web SDK imports
  381.   const {
  382.     doc,
  383.     setDoc,
  384.     getDoc,
  385.     collection,
  386.     addDoc,
  387.     serverTimestamp,
  388.     query,
  389.     where,
  390.     orderBy,
  391.     limit,
  392.     startAfter,
  393.     startAt,
  394.     endBefore,
  395.     endAt,
  396.     getDocs,
  397.     onSnapshot,
  398.     updateDoc,
  399.     arrayUnion,
  400.     arrayRemove,
  401.     deleteDoc,
  402.     writeBatch,
  403.     batchSetDoc,
  404.     batchUpdateDoc,
  405.     batchDeleteDoc,
  406.     batchCommit
  407.   } = require('firebase/firestore');
  408.  
  409.   firestoreDbInstance = db;
  410.  
  411.   // Store web Firestore functions for later use
  412.   firestoreFunctionsCompat = {
  413.     doc,
  414.     setDoc,
  415.     getDoc,
  416.     collection,
  417.     addDoc,
  418.     serverTimestamp,
  419.     query,
  420.     where,
  421.     orderBy,
  422.     limit,
  423.     startAfter,
  424.     startAt,
  425.     endBefore,
  426.     endAt,
  427.     getDocs,
  428.     onSnapshot,
  429.     updateDoc,
  430.     arrayUnion,
  431.     arrayRemove,
  432.     deleteDoc,
  433.     writeBatch,
  434.     batchSetDoc,
  435.     batchUpdateDoc,
  436.     batchDeleteDoc,
  437.     batchCommit
  438.   };
  439. } else {
  440.   // React Native Firebase SDK imports
  441.   const firestoreRNFB = require('@react-native-firebase/firestore').default;
  442.   firestoreDbInstance = db;
  443.  
  444.   const {
  445.     serverTimestamp,
  446.   } = require('@react-native-firebase/firestore');
  447.  
  448.   // Helper function to create a constraint object
  449.   const createConstraint = (type: 'where' | 'orderBy' | 'limit' | 'startAfter' | 'startAt' | 'endBefore' | 'endAt', args: any[]): QueryConstraint => {
  450.     return { type, args };
  451.   };
  452.  
  453.   // Universal collection function for React Native Firebase
  454.   const universalCollection = (db: any, path: string, ...segments: string[]): any => {
  455.     // For React Native Firebase, we ignore the db parameter and use the firestore instance
  456.     let ref = firestoreDbInstance;
  457.    
  458.     // If no segments are provided, just return the collection reference
  459.     if (segments.length === 0) {
  460.       return ref.collection(path);
  461.     }
  462.    
  463.     // Handle deep nesting paths similar to doc()
  464.     let allSegments = [path, ...segments];
  465.    
  466.     // Process segments in collection/document pairs
  467.     // For collection, we always need to end with a collection reference
  468.     for (let i = 0; i < allSegments.length; i++) {
  469.       if (i === 0 || i % 2 === 0) {
  470.         // Even indices (0, 2, 4...) are collections
  471.         ref = ref.collection(allSegments[i]);
  472.       } else {
  473.         // Odd indices (1, 3, 5...) are documents
  474.         ref = ref.doc(allSegments[i]);
  475.       }
  476.     }
  477.    
  478.     return ref;
  479.   };
  480.  
  481.   // Universal document reference function for React Native Firebase
  482.   const doc = (db: any, path: string, ...segments: string[]): any => {
  483.     // For React Native Firebase, we ignore the db parameter and use the firestore instance
  484.     let ref = firestoreDbInstance;
  485.    
  486.     // Handle paths like doc(db, 'collection') - should return a document with auto-ID
  487.     if (segments.length === 0) {
  488.       return ref.collection(path).doc();
  489.     }
  490.    
  491.     // Handle paths like doc(db, 'collection', 'docId')
  492.     if (segments.length === 1) {
  493.       return ref.collection(path).doc(segments[0]);
  494.     }
  495.    
  496.     // Handle deep nesting paths like doc(db, 'orgs', 'org1', 'projects', 'proj2')
  497.     let allSegments = [path, ...segments];
  498.    
  499.     // Process segments in collection/document pairs
  500.     for (let i = 0; i < allSegments.length; i += 2) {
  501.       const collectionName = allSegments[i];
  502.      
  503.       // If we have a collection but no document ID after it, return a reference with auto-ID
  504.       if (i + 1 >= allSegments.length) {
  505.         ref = ref.collection(collectionName).doc();
  506.         break;
  507.       }
  508.      
  509.       const documentId = allSegments[i + 1];
  510.       ref = ref.collection(collectionName).doc(documentId);
  511.     }
  512.    
  513.     return ref;
  514.   };
  515.  
  516.   // Implementation of setDoc for React Native Firebase
  517.   const setDoc = async (docRef: any, data: any, options?: { merge?: boolean }): Promise<void> => {
  518.     return docRef.set(data, options);
  519.   };
  520.  
  521.   // Implementation of getDoc for React Native Firebase
  522.   const getDoc = async (docRef: any): Promise<any> => {
  523.     try {
  524.       const snapshot = await docRef.get();
  525.       return createWebSDKCompatibleSnapshot(snapshot, docRef);
  526.     } catch (error) {
  527.       console.error('Error in getDoc:', error);
  528.       // Return a non-existent snapshot
  529.       return createWebSDKCompatibleSnapshot(null, docRef);
  530.     }
  531.   };
  532.  
  533.   // Implementation of addDoc for React Native Firebase
  534.   const addDoc = async (collectionRef: any, data: any): Promise<any> => {
  535.     // React Native Firebase's add method returns a DocumentReference with an auto-generated ID
  536.     const docRef = await collectionRef.add(data);
  537.    
  538.     // Ensure the returned reference has all expected properties
  539.     if (!docRef.id && docRef.path) {
  540.       // Extract the document ID from the path if it's missing
  541.       const pathSegments = docRef.path.split('/');
  542.       docRef.id = pathSegments[pathSegments.length - 1];
  543.     }
  544.    
  545.     return docRef;
  546.   };
  547.  
  548.   // Store RN Firestore functions for later use
  549.   firestoreFunctionsCompat = {
  550.     doc,  
  551.     setDoc,
  552.     getDoc,
  553.     collection: universalCollection,
  554.     addDoc,
  555.     serverTimestamp,
  556.     // For React Native Firebase, we need to handle the query constraints differently
  557.     query: (ref: any, ...queryConstraints: any[]) => {
  558.       console.log('[FirestoreCompat][query] Starting query with constraints:', {
  559.         refPath: ref?.path || 'unknown',
  560.         constraintsCount: queryConstraints.length,
  561.         constraints: queryConstraints.map(c => {
  562.           if (typeof c === 'object' && c !== null) {
  563.             return { type: c.type, args: c.args };
  564.           }
  565.           return typeof c;
  566.         })
  567.       });
  568.      
  569.       let queryRef = ref;
  570.    
  571.       for (const constraint of queryConstraints) {
  572.         if (!constraint) {
  573.           console.log('[FirestoreCompat][query] Skipping null/undefined constraint');
  574.           continue;
  575.         }
  576.    
  577.         // New constraint object format (preferred)
  578.         if (typeof constraint === 'object' && 'type' in constraint && Array.isArray(constraint.args)) {
  579.           const { type, args } = constraint;
  580.           console.log(`[FirestoreCompat][query] Processing constraint: ${type}`, { args });
  581.    
  582.           switch (type) {
  583.             case 'where':
  584.               console.log(`[FirestoreCompat][query] Applying where constraint: ${args[0]} ${args[1]} ${JSON.stringify(args[2])}`);
  585.               queryRef = queryRef.where(...args);
  586.               console.log('[FirestoreCompat][query] Updated query reference:', {
  587.                 path: queryRef?.path || 'unknown',
  588.                 hasWhere: typeof queryRef?.where === 'function',
  589.                 hasOrderBy: typeof queryRef?.orderBy === 'function',
  590.                 hasLimit: typeof queryRef?.limit === 'function'
  591.               });
  592.               break;
  593.             case 'orderBy':
  594.               console.log(`[FirestoreCompat][query] Applying orderBy constraint: ${args[0]} ${args[1] || 'asc'}`);
  595.               queryRef = queryRef.orderBy(...args);
  596.               break;
  597.             case 'limit':
  598.               console.log(`[FirestoreCompat][query] Applying limit constraint: ${args[0]}`);
  599.               queryRef = queryRef.limit(...args);
  600.               break;
  601.             case 'startAfter':
  602.               console.log(`[FirestoreCompat][query] Applying startAfter constraint: ${JSON.stringify(args[0])}`);
  603.               queryRef = queryRef.startAfter(...args);
  604.               break;
  605.             case 'startAt':
  606.               console.log(`[FirestoreCompat][query] Applying startAt constraint: ${JSON.stringify(args[0])}`);
  607.               queryRef = queryRef.startAt(...args);
  608.               break;
  609.             case 'endBefore':
  610.               console.log(`[FirestoreCompat][query] Applying endBefore constraint: ${JSON.stringify(args[0])}`);
  611.               queryRef = queryRef.endBefore(...args);
  612.               break;
  613.             case 'endAt':
  614.               console.log(`[FirestoreCompat][query] Applying endAt constraint: ${JSON.stringify(args[0])}`);
  615.               queryRef = queryRef.endAt(...args);
  616.               break;
  617.             default:
  618.               console.warn(`[FirestoreCompat][query] Unsupported query constraint type: ${type}`);
  619.           }
  620.         }
  621.       }
  622.    
  623.       console.log('[FirestoreCompat][query] Final query reference:', {
  624.         path: queryRef?.path || 'unknown',
  625.         hasWhere: typeof queryRef?.where === 'function',
  626.         hasOrderBy: typeof queryRef?.orderBy === 'function',
  627.         hasLimit: typeof queryRef?.limit === 'function'
  628.       });
  629.      
  630.       return queryRef;
  631.     },
  632.     // For React Native Firebase, we need to return a constraint object instead of applying it directly
  633.     where: (field: string, opStr: string, value: any) => {
  634.       return createConstraint('where', [field, opStr, value]);
  635.     },
  636.     // Add support for additional query constraints
  637.     orderBy: (field: string, direction: 'asc' | 'desc' = 'asc') => {
  638.       return createConstraint('orderBy', [field, direction]);
  639.     },
  640.     limit: (limit: number) => {
  641.       return createConstraint('limit', [limit]);
  642.     },
  643.     startAfter: (value: any) => {
  644.       return createConstraint('startAfter', [value]);
  645.     },
  646.     startAt: (value: any) => {
  647.       return createConstraint('startAt', [value]);
  648.     },
  649.     endBefore: (value: any) => {
  650.       return createConstraint('endBefore', [value]);
  651.     },
  652.     endAt: (value: any) => {
  653.       return createConstraint('endAt', [value]);
  654.     },
  655.     getDocs: async (queryRef: any) => {
  656.       try {
  657.         console.log('[FirestoreCompat][getDocs] Query reference:', {
  658.           path: queryRef?.path || 'unknown',
  659.           hasWhere: typeof queryRef?.where === 'function',
  660.           hasOrderBy: typeof queryRef?.orderBy === 'function',
  661.           hasLimit: typeof queryRef?.limit === 'function'
  662.         });
  663.         const snapshot = await queryRef.get();
  664.         return createWebSDKCompatibleQuerySnapshot(snapshot);
  665.       } catch (error) {
  666.         // Bug: queryRef.path = unknown
  667.           // Happens when
  668.         console.log('Error in getDocs:', queryRef);
  669.         console.error('Error in getDocs:', error);
  670.         // Return an empty query snapshot
  671.         return createWebSDKCompatibleQuerySnapshot(null);
  672.       }
  673.     },
  674.     onSnapshot: wrappedOnSnapshot,
  675.     updateDoc: (ref: any, data: any) => ref.update(data),
  676.     arrayUnion: (...values: any[]) => {
  677.       return firestoreRNFB.FieldValue.arrayUnion(...values);
  678.     },
  679.     arrayRemove: (...values: any[]) => {
  680.       return firestoreRNFB.FieldValue.arrayRemove(...values);
  681.     },
  682.     deleteDoc: (ref: any) => ref.delete(),
  683.     // Batch operations for React Native Firebase
  684.     writeBatch: (db: any) => {
  685.       // For React Native Firebase, we ignore the db parameter and use the firestore instance
  686.       return firestoreDbInstance.batch();
  687.     },
  688.     batchSetDoc: (batch, ref, data, options) => {
  689.       batch.set(ref, data, options);
  690.     },
  691.     batchUpdateDoc: (batch, ref, data) => {
  692.       batch.update(ref, data);
  693.     },
  694.     batchDeleteDoc: (batch, ref) => {
  695.       batch.delete(ref);
  696.     },
  697.     batchCommit: (batch) => {
  698.       return batch.commit();
  699.     }
  700.   };
  701. }
  702.  
  703. // Export the Firestore instance and functions
  704. export { firestoreDbInstance, firestoreFunctionsCompat };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement