如何在TypeScript Firebase SDK与Admin SDK间共享Firestore集合路径及模型类型映射
如何在TypeScript版Firebase SDK与Firebase Admin SDK之间共享Firestore集合路径和模型类型映射?
现有问题示例
当前代码可正常运行,但未实现集合路径与模型类型的统一映射(类似{"courses": Course, "sections": Section}),导致客户端与Admin端的ORM代码存在重复逻辑:
types.ts
export interface Course { id: string; } export interface Section { id: string; }
collectionPath.ts
export const collectionPaths = { courses: "courses", sections: "sections", };
orm.ts(客户端Firebase SDK)
import { collection, Firestore, FirestoreDataConverter, PartialWithFieldValue, QueryDocumentSnapshot, DocumentData, } from "firebase/firestore"; import { collectionPaths } from "./collectionPaths"; import { Course, Section } from "./types"; const converter = <T>(): FirestoreDataConverter<T> => ({ toFirestore: (data: PartialWithFieldValue<T>) => data as DocumentData, fromFirestore: (snap: QueryDocumentSnapshot) => snap.data() as T, }); export const createClientOrm = (firestore: Firestore) => { const dataPoint = <T>(collectionPath: string) => collection(firestore, collectionPath).withConverter(converter<T>()); return { courses: dataPoint<Course>(collectionPaths.courses), sections: dataPoint<Section>(collectionPaths.sections), }; };
adminOrm.ts(Firebase Admin SDK)
import { firestore } from "firebase-admin"; import { FirestoreDataConverter, WithFieldValue, QueryDocumentSnapshot, DocumentData, } from "firebase-admin/firestore"; import { collectionPaths } from "./collectionPaths"; import { Course, Section } from "./types"; const converter = <T>(): FirestoreDataConverter<T> => ({ toFirestore: (data: WithFieldValue<T>) => data as DocumentData, fromFirestore: (snap: QueryDocumentSnapshot) => snap.data() as T, }); export const createAdminOrm = (firestore: firestore.Firestore) => { const dataPoint = <T>(collectionPath: string) => firestore.collection(collectionPath).withConverter(converter<T>()); return { courses: dataPoint<Course>(collectionPaths.courses), sections: dataPoint<Section>(collectionPaths.sections), }; };
已尝试方案(存在类型丢失问题)
尝试通过CollectionPath<T>类绑定路径与类型,但最终生成的ORM对象丢失了CollectionReference<ModelType>的泛型类型信息:
collectionPaths.ts
import { Course, Section } from "./types"; export class CollectionPath<T> { path: string; constructor(path: string) { this.path = path; } } export const collectionPaths = { courses: new CollectionPath<Course>("courses"), sections: new CollectionPath<Section>("sections"), };
adminOrm.ts
import { firestore } from "firebase-admin"; import { FirestoreDataConverter, WithFieldValue, QueryDocumentSnapshot, DocumentData, CollectionReference, } from "firebase-admin/firestore"; import { collectionPaths } from "./collectionPaths"; const converter = <T>(): FirestoreDataConverter<T> => ({ toFirestore: (data: WithFieldValue<T>) => data as DocumentData, fromFirestore: (snap: QueryDocumentSnapshot) => snap.data() as T, }); export const createAdminOrm = (firestore: firestore.Firestore) => { const dataPoint = <T>(collectionPath: string) => firestore.collection(collectionPath).withConverter(converter<T>()); return <{ [key in keyof typeof collectionPaths]: CollectionReference }>( Object.entries(collectionPaths).reduce( (p, [k, v]) => Object.assign(p, { [k]: dataPoint(v.path) }), {} ) ); };
解决方案
通过定义集合类型映射接口+类型工具函数实现路径与类型的统一映射,同时抽离通用转换器逻辑,确保类型信息完整保留:
1. 集中管理集合路径与类型映射
新建collections.ts,统一维护集合配置:
import { Course, Section } from "./types"; // 定义集合名称到模型类型的映射 export type CollectionMap = { courses: Course; sections: Section; }; // 定义集合路径(与类型映射一一对应) export const collectionPaths: Record<keyof CollectionMap, string> = { courses: "courses", sections: "sections", };
2. 抽离通用转换器(适配两个SDK差异)
新建converters.ts,分别提供客户端和Admin SDK的通用转换器:
import { FirestoreDataConverter, PartialWithFieldValue, QueryDocumentSnapshot, DocumentData, } from "firebase/firestore"; import { FirestoreDataConverter as AdminFirestoreDataConverter, WithFieldValue as AdminWithFieldValue, QueryDocumentSnapshot as AdminQueryDocumentSnapshot, } from "firebase-admin/firestore"; // 客户端SDK转换器 export const createClientConverter = <T>(): FirestoreDataConverter<T> => ({ toFirestore: (data: PartialWithFieldValue<T>) => data as DocumentData, fromFirestore: (snap: QueryDocumentSnapshot) => snap.data() as T, }); // Admin SDK转换器 export const createAdminConverter = <T>(): AdminFirestoreDataConverter<T> => ({ toFirestore: (data: AdminWithFieldValue<T>) => data as DocumentData, fromFirestore: (snap: AdminQueryDocumentSnapshot) => snap.data() as T, });
3. 生成带类型的客户端ORM
修改orm.ts:
import { collection, Firestore, CollectionReference } from "firebase/firestore"; import { CollectionMap, collectionPaths } from "./collections"; import { createClientConverter } from "./converters"; // 定义客户端ORM的强类型结构 type ClientOrm = { [K in keyof CollectionMap]: CollectionReference<CollectionMap[K]>; }; export const createClientOrm = (firestore: Firestore): ClientOrm => { const orm: Partial<ClientOrm> = {}; // 遍历集合配置自动生成带类型的CollectionReference (Object.keys(collectionPaths) as Array<keyof CollectionMap>).forEach(key => { const path = collectionPaths[key]; orm[key] = collection(firestore, path).withConverter(createClientConverter<CollectionMap[key]>()); }); return orm as ClientOrm; };
4. 生成带类型的Admin SDK ORM
修改adminOrm.ts:
import { firestore, CollectionReference } from "firebase-admin/firestore"; import { CollectionMap, collectionPaths } from "./collections"; import { createAdminConverter } from "./converters"; // 定义Admin ORM的强类型结构 type AdminOrm = { [K in keyof CollectionMap]: CollectionReference<CollectionMap[K]>; }; export const createAdminOrm = (firestore: firestore.Firestore): AdminOrm => { const orm: Partial<AdminOrm> = {}; // 遍历集合配置自动生成带类型的CollectionReference (Object.keys(collectionPaths) as Array<keyof CollectionMap>).forEach(key => { const path = collectionPaths[key]; orm[key] = firestore.collection(path).withConverter(createAdminConverter<CollectionMap[key]>()); }); return orm as AdminOrm; };
方案优势
- 单一数据源:集合路径和类型映射集中管理,修改时只需改动一处
- 类型安全:自动推导每个集合对应的模型类型,避免手动指定的错误
- 减少重复:抽离通用转换器逻辑,客户端与Admin SDK复用核心配置
- 扩展性强:新增集合时,只需在
CollectionMap和collectionPaths中添加对应项,ORM会自动生成对应接口
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

