How do I share a FirestoreDataConverter between @firebase/firestore and...

Discussão em 'Angular' iniciado por Igor Augustyński, Fevereiro 16, 2025.

  1. I am writing an Angular application which uses Firestore. I want to be able to use my interfaces and FirestoreDataConverters (which are declared in an Angular Library) in my Firebase Functions too. The main issue is that that the QueryDocumentSnapshot class has slightly different implementations in firebase-admin/firestore and @firebase/firestore.

    I'm trying to come up with a solution based on this comment on a GitHub Issue.

    So far I have declared a new QueryDocumentSnapshot by doing:

    import { QueryDocumentSnapshot as QueryDocumentSnapshotFire } from '@angular/fire/firestore';
    import { QueryDocumentSnapshot as QueryDocumentSnapshotAdmin } from '@google-cloud/firestore';

    export type QueryDocumentSnapshot = QueryDocumentSnapshotAdmin | QueryDocumentSnapshotFire;


    then, I have declared my types and converter as follows:

    export type FirestoreUser = z.infer<typeof firestoreUserSchema>;
    export const firestoreUserSchema = z.object({
    email: z.string(),
    });

    export type FirestoreUserDb = z.infer<typeof firestoreUserDbSchema>;
    export const firestoreUserDbSchema = z.object({
    email: z.string(),
    });

    export const firestoreUserConverter: FirestoreDataConverter<FirestoreUser, FirestoreUserDb> = {
    fromFirestore: (snapshot: QueryDocumentSnapshot): FirestoreUser => {
    const validFirestoreUserDb = firestoreUserDbSchema.parse(snapshot.data());

    return {
    email: validFirestoreUserDb.email,
    };
    },
    toFirestore: (firestoreUser: FirestoreUser): FirestoreUserDb => {
    const validFirestoreUser = firestoreUserSchema.parse(firestoreUser);

    return {
    email: validFirestoreUser.email,
    };
    },
    };


    now, I am able to use my firestoreUserConverter on the frontend:

    const userDocRef = doc(this.firestore, FirestoreCollections.Users, uid).withConverter(firestoreUserConverter);


    where the type of userDocRef is correctly inferred as

    const userDocRef: DocumentReference<{
    email: string;
    }, {
    email: string;
    }>


    but, in my Firebase Functions, when I try to do something like

    const result = await db.collection(FirestoreCollections.Users).doc(user.uid).withConverter(firestoreUserConverter).set({
    email: null, // This should only accept string
    });


    I do not get an error saying "Cannot assign null to string" because the .set method does not infer any types from the converter. I am 100% sure that this is possible because during my experiments I briefly achieved the aforementioned error but have since changed things up and have no idea what went wrong.

    The problem is that the DocumentReference returned from withConverter is not typed correctly when using firebase-admin unless I type it explicitly:

    const result = await db.collection(FirestoreCollections.Users).withConverter<FirestoreUser, FirestoreUserDb>(firestoreUserConverter).doc(user.uid).set({
    email: user.email,
    });


    when typed explicitly, the returned DocumentReference is of type

    FirebaseFirestore.DocumentReference<{
    email: string;
    }, {
    email: string;
    }>


    but if I do not type it, it's just

    FirebaseFirestore.DocumentReference<unknown, FirebaseFirestore.DocumentData>


    while on the frontend the type is correctly inferred from the converter function even when I don't type the withConverter function explicitly.

    My questions are:

    1. Why is the type not inferred correctly when calling set()?
    2. Is there a better way to achieve sharing FirestoreDataConverters between my FE and BE implementations? I don't really like importing @google-cloud/firestore in my library.
    3. Should I change approach completely and use something like Typesaurus instead?

    Continue reading...

Compartilhe esta Página