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

Drizzle ORM中PgTable名称获取异常:类型与运行时不符的解决问询

解决Drizzle ORM外键工具函数运行时表名获取错误问题

问题分析

你遇到的TypeError: Cannot read properties of undefined (reading 'name')核心原因有两个:

  • TypeScript类型定义里PgTableWithColumns声明了_属性,但Drizzle在运行时并未将表名暴露在_字段中,而是存放在内部私有Symbol键Symbol(drizzle:BaseName)下,直接访问table._.name自然会拿到undefined。
  • 代码里存在拼写错误:snakeCase()}_id应该是snakeCase(tableName)_id`,这也会导致后续列名生成错误。

解决方案

方案1:通过内部Symbol获取表名(适配当前Drizzle版本)

虽然Drizzle没有公开这个Symbol,但可以通过遍历对象的Symbol属性定位到它,从而获取表名:

import {
  type PgColumn,
  type PgTableWithColumns,
  pgTableCreator,
  serial,
  bigint,
  varchar,
} from "drizzle-orm/pg-core";

import { camelCase, snakeCase } from "lodash";

export const pgTable = pgTableCreator((name) => {
  return `some-app_${name}`;
});

export function id() {
  return serial("id").primaryKey();
}

type TableWithId = PgTableWithColumns<{
  name: string;
  schema: string | undefined;
  dialect: "pg";
  columns: {
    id: PgColumn;
  };
}>;

// 定位Drizzle内部的表名Symbol
const getTableNameSymbol = (table: TableWithId): symbol => {
  return Object.getOwnPropertySymbols(table).find(sym => sym.description === "drizzle:BaseName")!;
};

export function fk(...relations: (TableWithId | TableWithId[])[]) {
  return Object.fromEntries(
    relations.flatMap((entry) => {
      const isOptional = Array.isArray(entry);
      const tables = isOptional ? entry : [entry];

      return tables.map((table) => {
        // 获取原始表名
        const tableName = table[getTableNameSymbol(table)] as string;
        // 生成蛇形命名的外键列名
        const columnName = `${snakeCase(tableName)}_id`;
        // 生成驼峰式的模型属性名
        const propertyName = `${camelCase(tableName)}Id`;
        const column = bigint(columnName, { mode: "number" }).references(() => table.id);

        return [propertyName, isOptional ? column : column.notNull()];
      });
    }),
  );
}

// 补充示例timestamps函数(按需使用)
export function timestamps() {
  return {
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  };
}

方案2:自定义表创建函数,附加可访问的表名(更稳定)

依赖内部Symbol的方式可能随Drizzle版本更新失效,更稳妥的方式是在创建表时手动添加可访问的表名属性:

import {
  type PgColumn,
  type PgTableWithColumns,
  pgTableCreator,
  serial,
  bigint,
} from "drizzle-orm/pg-core";

import { camelCase, snakeCase } from "lodash";

const basePgTable = pgTableCreator((name) => `some-app_${name}`);

// 自定义表创建函数,附加公开的表名属性
export const pgTable = <T extends Record<string, any>>(name: string, columns: T) => {
  const table = basePgTable(name, columns);
  // 添加可直接访问的表名属性
  (table as any)._publicTableName = name;
  return table as typeof table & { _publicTableName: string };
};

export function id() {
  return serial("id").primaryKey();
}

// 更新类型定义,包含自定义表名属性
type TableWithId = PgTableWithColumns<{
  name: string;
  schema: string | undefined;
  dialect: "pg";
  columns: { id: PgColumn };
}> & { _publicTableName: string };

export function fk(...relations: (TableWithId | TableWithId[])[]) {
  return Object.fromEntries(
    relations.flatMap((entry) => {
      const isOptional = Array.isArray(entry);
      const tables = isOptional ? entry : [entry];

      return tables.map((table) => {
        const tableName = table._publicTableName;
        const columnName = `${snakeCase(tableName)}_id`;
        const propertyName = `${camelCase(tableName)}Id`;
        const column = bigint(columnName, { mode: "number" }).references(() => table.id);

        return [propertyName, isOptional ? column : column.notNull()];
      });
    }),
  );
}

验证使用

按你的预期方式使用即可,修复后不会再出现表名未定义的错误:

// 先定义关联表示例
export const phone = pgTable("phones", { id: id(), number: varchar("number", { length: 20 }).notNull() });
export const email = pgTable("emails", { id: id(), address: varchar("address", { length: 255 }).notNull() });
export const address = pgTable("addresses", { id: id(), street: varchar("street", { length: 255 }).notNull() });

// 主表使用fk函数
export const contact = pgTable("contacts", {
  id: id(),
  firstName: varchar("first_name", { length: 128 }).notNull(),
  lastName: varchar("last_name", { length: 128 }).notNull(),
  ...fk(phone, [email], [address]),
  ...timestamps(),
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:55:07