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

TypeScript中基于MGroup属性约束insertRelation的field参数

利用TypeScript类型系统限制MongoDB数据库层的参数冲突

我们正在为MongoDB构建数据库层,希望通过TypeScript的类型系统避免参数冲突。每个文档类型(MSite、MOrganization等)包含users和/或machines属性,需要实现API根据传入的MGroup类型,限制field参数的可选值。

具体需求示例:

  • MRegion包含users但不包含machines,传入match.type="region"时,field不能设为machines
  • MSite同时包含users和machines,两种field值都允许
  • MSystem不包含这两个属性,传入该类型时field无合法值

当前函数实现如下:

export type MGroup = MSite | MOrganization | MRegion | MDistributor | MSystem

export async function insertRelation<
  T extends MRelation
>({
  match,
  field,
  relation,
}: {
  match: Filter<MGroup> & { type: string; _id: string }
  field: 'users' | 'machines'
  relation: Omit<T, AutomatedProperties>
}): Promise<void> {
  ...function body...
}

我尝试过条件类型和函数重载两种方案,但始终无法正确设置类型,函数内部会出现类型不一致的错误,恳请帮助。

以下是简化的类型定义和当前函数:

// 实际项目中Filter类型来自MongoDB,此处做简化
type Filter<T> = {
}

type MSite = {
    parent: string
    type: "site"
    users: Array<any>
    machines: Array<any>
}

type MOrganization = {
    parent: string
    type: "organization"
    users: Array<any>
    machines: Array<any>
}

type MRegion = {
    parent: string
    type: "region"
    users: Array<any>
}

type MDistributor = {
    parent: string
    type: "distributor"
    users: Array<any>
}

type MSystem = {
    type: "system"
}

export type MGroup = MSite | MOrganization | MRegion | MDistributor | MSystem

export async function insertRelation({
  match,
  field,
}: {
  match: Filter<MGroup> & { type: string; _id: string }
  field: 'users' | 'machines'
}): Promise<void> {
}

// 这段代码应该触发类型错误,但目前没有
await insertRelation({ match: { type: 'distributor', _id: 'test' }, field: 'machines' })

解决方案

方案1:函数重载+类型推导

为每个MGroup子类型定义明确的函数重载,绑定match.type与允许的field值:

// 定义每个类型允许的field集合
type AllowedFields<T> = 
  T extends { users: any } ? 'users' : never |
  T extends { machines: any } ? 'machines' : never;

// 为每个子类型定义重载
export async function insertRelation(match: Filter<MSite> & { type: "site"; _id: string }, field: AllowedFields<MSite>): Promise<void>;
export async function insertRelation(match: Filter<MOrganization> & { type: "organization"; _id: string }, field: AllowedFields<MOrganization>): Promise<void>;
export async function insertRelation(match: Filter<MRegion> & { type: "region"; _id: string }, field: AllowedFields<MRegion>): Promise<void>;
export async function insertRelation(match: Filter<MDistributor> & { type: "distributor"; _id: string }, field: AllowedFields<MDistributor>): Promise<void>;
export async function insertRelation(match: Filter<MSystem> & { type: "system"; _id: string }, field: never): Promise<void>;

// 函数实现
export async function insertRelation({
  match,
  field,
}: {
  match: Filter<MGroup> & { type: string; _id: string }
  field: 'users' | 'machines'
}): Promise<void> {
  // 内部通过类型守卫处理不同逻辑
  switch(match.type) {
    case "site":
    case "organization":
      // 此处field为'users'|'machines'
      break;
    case "region":
    case "distributor":
      // 此处field仅为'users'
      break;
    case "system":
      throw new Error("MSystem不支持users或machines字段");
  }
}

// 现在会触发类型错误:"machines"不能赋给"users"类型参数
await insertRelation({ match: { type: 'distributor', _id: 'test' }, field: 'machines' })

方案2:泛型约束+类型映射

通过类型映射绑定type字符串与对应子类型,再推导允许的field:

// 建立type到子类型的映射
type GroupTypeMap = {
  "site": MSite,
  "organization": MOrganization,
  "region": MRegion,
  "distributor": MDistributor,
  "system": MSystem
}

// 提取当前类型允许的field
type AllowedFields<T> = keyof Pick<T, 'users' | 'machines'>;

export async function insertRelation<K extends keyof GroupTypeMap>({
  match,
  field,
}: {
  match: Filter<GroupTypeMap[K]> & { type: K; _id: string }
  field: AllowedFields<GroupTypeMap[K]>
}): Promise<void> {
  // 内部类型完全安全
  if (match.type === "system") {
    throw new Error("MSystem无合法字段");
  }
}

// 触发类型错误:"machines"不符合MDistributor的允许字段
await insertRelation({ match: { type: 'distributor', _id: 'test' }, field: 'machines' })

两种方案均可实现需求:

  • 方案1直观易懂,适合类型数量较少的场景
  • 方案2更简洁,新增MGroup子类型时只需扩展GroupTypeMap即可

内容的提问来源于stack exchange,提问作者Tx3

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 07:21:37