I am using the Google Firestore Emulator locally for development. However, when I query the database data, I find that the results are inconsistent and not correct each time. Here is the detailed information.
1.This is my firestore setup:
/**
* Firebase configuration for both local development and production
*/
import { initializeApp, getApps, FirebaseApp } from 'firebase/app';
import { getFirestore, connectFirestoreEmulator, Firestore } from 'firebase/firestore';
import { getAuth, connectAuthEmulator, Auth } from 'firebase/auth';
import logger from 'src/logger.ts';
// Firebase configuration
const firebaseConfig = {
// For local development with emulator, these can be dummy values
apiKey: process.env.FIREBASE_API_KEY || 'dummy-api-key',
authDomain: process.env.FIREBASE_AUTH_DOMAIN || 'dummy-project.firebaseapp.com',
projectId: process.env.FIREBASE_PROJECT_ID || 'dummy-project',
storageBucket: process.env.FIREBASE_STORAGE_BUCKET || 'dummy-project.appspot.com',
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID || '123456789',
appId: process.env.FIREBASE_APP_ID || 'dummy-app-id',
};
// Initialize Firebase app
let app: FirebaseApp;
if (getApps().length === 0) {
app = initializeApp(firebaseConfig);
logger.info('Firebase app initialized');
} else {
app = getApps()[0];
logger.info('Using existing Firebase app');
}
// Initialize Firestore
export const db: Firestore = getFirestore(app);
// Initialize Auth
export const auth: Auth = getAuth(app);
// Connect to emulators in development
if (process.env.NODE_ENV === 'development') {
try {
// Always connect to emulators in development
connectFirestoreEmulator(db, 'localhost', 8080);
logger.info('Connected to Firestore emulator on localhost:8080');
connectAuthEmulator(auth, 'http://localhost:9099');
logger.info('Connected to Auth emulator on localhost:9099');
} catch (error) {
logger.warn(`Could not connect to Firebase emulators: ${error}`);
logger.info('Make sure to start Firebase emulators with: firebase emulators:start');
}
}
export { app };
2.Then I start the Emulator by make firebase emulators:start --only firestore --project dummy-project
3.I do something and there are some items in my database.
item1: {...., expiredAt: {nanoseconds: 123000000, seconds: 1757341144}}
item2: {...., expiredAt: {nanoseconds: 76000000, seconds: 1757341146}}
item3: {...., expiredAt: {nanoseconds: 200000000, seconds: 1757341133}}
4.I try to get these 3 items by executing findUnexpiredThreads() function:
import {
Firestore,
collection,
doc,
setDoc,
getDoc,
getDocs,
updateDoc,
query,
where,
orderBy,
limit,
getCountFromServer,
} from 'firebase/firestore';
async findUnexpiredThreads(): Promise<IChatThread[]> {
try {
const q = query(
collection(this.db, this.threadsCollection),
where('expiresAt', '>', toFirestoreSafe(new Date()))
);
const snapshot = await getDocs(q);
return snapshot.docs.map(doc => fromFirestoreSafe(doc.data())) as IChatThread[];
} catch (error) {
logger.error(`Failed to find unexpired chat threads: ${error}`);
return [];
}
}
// Convert app data to Firestore-safe values: BigInt -> string, Date -> Timestamp
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function toFirestoreSafe<T = unknown>(value: T): any {
return deepMap(value as unknown as JsonValue, v => {
if (typeof v === 'bigint') {
return v.toString();
}
if (v instanceof Date) {
return Timestamp.fromDate(v);
}
return v;
});
}
// Convert Firestore data to app-friendly values: Timestamp -> Date
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function fromFirestoreSafe<T = unknown>(value: T): any {
return deepMap(value as unknown as JsonValue, v => {
if (v instanceof Timestamp) {
return v.toDate();
}
return v;
});
}
Result: Sometimes it returns 0 items, sometimes it returns 1/2/3 items, the result is unstable. In theory, it should always return 3 items, because the expiredAt of these 3 items is greater than now.
I'm not sure where the problem lies. I checked the contents of the database, and they haven't been modified. I was merely making queries, but I don't know why the results are incorrect, and the results of each query are also inconsistent.
I discovered that when the problem occurred, I stored expiredAt as a Map format (item1: {...., expiredAt: {nanoseconds: 123000000, seconds: 1757341144}}). I fixed it by not converting Date and timestamp types to maps when encountering them in toFirestoreSafe() and fromFirestoreSafe().