You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在@firebase/firestore与firebase-admin间共享FirestoreDataConverter?

Firestore前后端转换器共享与类型推断问题

问题背景

我正在开发一个使用Firestore的Angular应用,希望在Firebase Functions中复用Angular库定义的接口和FirestoreDataConverter。核心障碍是QueryDocumentSnapshot类在firebase-admin/firestore和@firebase/firestore中的实现存在差异。

我的尝试

为了兼容两个环境,我基于GitHub Issue的评论声明了联合类型:

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

export type QueryDocumentSnapshot = QueryDocumentSnapshotAdmin | QueryDocumentSnapshotFire;

接着用Zod定义了业务类型和转换器:

import { z } from 'zod';
import { FirestoreDataConverter } from '@angular/fire/firestore';

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 };
  },
};

不同环境的表现

前端(Angular)

使用转换器时,DocumentReference类型能正确自动推断:

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

userDocRef的类型为:

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

Firebase Functions(Admin)

调用set()时类型无法自动推断,传入不符合类型的值(比如null)也不会报错:

const result = await db.collection(FirestoreCollections.Users).doc(user.uid).withConverter(firestoreUserConverter).set({
  email: null, // 此处应仅接受string类型,但无类型提示
});

必须显式指定泛型参数,才能获得正确的类型检查:

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

显式指定后,DocumentReference类型才变为:

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

未指定泛型时,类型默认为:

FirebaseFirestore.DocumentReference<unknown, FirebaseFirestore.DocumentData>

疑问与解答

1. 为何调用set()时类型无法正确推断?

这是因为firebase-admin依赖的@google-cloud/firestore与前端@angular/fire依赖的@firebase/firestore,二者的FirestoreDataConverter属于不同的类型空间。你的转换器声明使用的是前端版本的FirestoreDataConverter,在Admin环境下TypeScript无法自动将其与Admin版本的转换器类型兼容,因此无法推导泛型参数。

联合类型QueryDocumentSnapshot只能解决快照类型的兼容,但无法抹平两个SDK在转换器接口上的类型差异,导致自动推断失效。

2. 有没有更优方案实现前后端FirestoreDataConverter共享?我不希望在库中导入@google-cloud/firestore。

可以通过类型隔离+适配器模式实现,彻底消除共享库对Firestore SDK的依赖:

步骤1:定义环境无关的共享逻辑

在共享库中只保留纯业务类型和转换函数,不引入任何Firestore SDK类型:

// src/shared/firestore/user-converter.ts
import { z } from 'zod';

// 纯业务类型
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 userFromFirestore = (data: FirestoreUserDb): FirestoreUser => {
  const validData = firestoreUserDbSchema.parse(data);
  return { email: validData.email };
};

export const userToFirestore = (user: FirestoreUser): FirestoreUserDb => {
  const validUser = firestoreUserSchema.parse(user);
  return { email: validUser.email };
};

步骤2:在前端和Admin中分别适配

  • 前端(Angular):
// 前端代码
import { FirestoreDataConverter } from '@angular/fire/firestore';
import { userFromFirestore, userToFirestore, FirestoreUser, FirestoreUserDb } from '../shared/firestore/user-converter';

export const firestoreUserConverter: FirestoreDataConverter<FirestoreUser, FirestoreUserDb> = {
  fromFirestore: (snapshot) => userFromFirestore(snapshot.data()),
  toFirestore: (user) => userToFirestore(user),
};
  • Firebase Functions(Admin):
// Admin代码
import { FirestoreDataConverter } from 'firebase-admin/firestore';
import { userFromFirestore, userToFirestore, FirestoreUser, FirestoreUserDb } from '../shared/firestore/user-converter';

export const firestoreUserConverter: FirestoreDataConverter<FirestoreUser, FirestoreUserDb> = {
  fromFirestore: (snapshot) => userFromFirestore(snapshot.data()),
  toFirestore: (user) => userToFirestore(user),
};

这种方案下,共享库完全独立于Firebase SDK,前端和Admin各自适配对应SDK的转换器接口,TypeScript能完美自动推导泛型,无需手动指定。

3. 是否应彻底更换方案,使用Typesaurus这类工具?

这取决于你的项目阶段和需求:

  • 如果项目处于初期,且对Firestore类型安全有极高要求,Typesaurus是不错的选择——它通过统一的类型系统封装了Firestore操作,天然支持前后端共享类型,不需要手动维护转换器,开发体验更流畅。
  • 如果项目已有一定规模,或依赖Angular Fire的现有集成,则没必要彻底切换。前面的适配器方案改动成本低,能快速解决共享问题,同时保留现有技术栈。

内容的提问来源于stack exchange,提问作者Igor Augustyński

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 10:10:08