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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 18:20:34