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

如何结合Supabase生成的数据库类型使用TypeScript泛型?

问题:Supabase泛型Insert类型不兼容错误

实现代码

type DbTables = Database['public']['Tables'];
type TableName = keyof DbTables;
type DbInsert<T extends TableName> = DbTables[T]['Insert'];

async function create<T extends TableName>(insertData: DbInsert<T> | DbInsert<T>[], resourceName: T) {
  try {
    const { data, error } = await supabase.from(resourceName).insert(insertData).select();
    if (error) throw error;
    return data;
  } catch (error) {
    console.error(error);
  }
}

数据库表结构

create table if not exists public.foo (
  id bigint unique generated always as identity,
  bar text not null,
  baz real [] not null,
  qux jsonb not null
);

生成的类型定义

export type Json = string | number | boolean | null | { [key: string]: Json } | Json[];

export interface Database {
  public: {
    Tables: {
      foo: {
        Row: {
          bar: string;
          baz: number[];
          id: number;
          qux: Json;
        };
        Insert: {
          bar: string;
          baz: number[];
          id?: never;
          qux: Json;
        };
        Update: {
          bar?: string;
          baz?: number[];
          id?: never;
          qux?: Json;
        };
      };
    };
    Views: {
      [_ in never]: never;
    };
    Functions: {
      [_ in never]: never;
    };
    Enums: {
      [_ in never]: never;
    };
  };
}

错误信息(翻译后)

类型“DbInsert<T> | DbInsert<T>[]”无法分配给参数类型“({ foo: { Row: { bar: string; baz: number[]; id: number; qux: Json; }; Insert: { bar: string; baz: number[]; id?: undefined; qux: Json; }; Update: { bar?: string | undefined; baz?: number[] | undefined; id?: undefined; qux?: Json | undefined; }; }; }[T] extends { ...; } ? { ...; }[T]["Insert"] : never) | ({ ...; }[T...'.
  类型“DbInsert<T>”无法分配给类型“({ foo: { Row: { bar: string; baz: number[]; id: number; qux: Json; }; Insert: { bar: string; baz: number[]; id?: undefined; qux: Json; }; Update: { bar?: string | undefined; baz?: number[] | undefined; id?: undefined; qux?: Json | undefined; }; }; }[T] extends { ...; } ? { ...; }[T]["Insert"] : never) | ({ ...; }[T...'.
    类型“{ bar: string; baz: number[]; id?: undefined; qux: Json; }”无法分配给类型“({ foo: { Row: { bar: string; baz: number[]; id: number; qux: Json; }; Insert: { bar: string; baz: number[]; id?: undefined; qux: Json; }; Update: { bar?: string | undefined; baz?: number[] | undefined; id?: undefined; qux?: Json | undefined; }; }; }[T] extends { ...; } ? { ...; }[T]["Insert"] : never) | ({ ...; }[T...'.
      类型“{ bar: string; baz: number[]; id?: undefined; qux: Json; }”无法分配给类型“{ foo: { Row: { bar: string; baz: number[]; id: number; qux: Json; }; Insert: { bar: string; baz: number[]; id?: undefined; qux: Json; }; Update: { bar?: string | undefined; baz?: number[] | undefined; id?: undefined; qux?: Json | undefined; }; }; }[T] extends { ...; } ? { ...; }[T]["Insert"] : never'.
        类型“DbInsert<T>”无法分配给类型“({ foo: { Row: { bar: string; baz: number[]; id: number; qux: Json; }; Insert: { bar: string; baz: number[]; id?: undefined; qux: Json; }; Update: { bar?: string | undefined; baz?: number[] | undefined; id?: undefined; qux?: Json | undefined; }; }; }[T] extends { ...; } ? { ...; }[T]["Insert"] : never)[]'.
          类型“{ bar: string; baz: number[]; id?: undefined; qux: Json; }”缺少类型“({ foo: { Row: { bar: string; baz: number[]; id: number; qux: Json; }; Insert: { bar: string; baz: number[]; id?: undefined; qux: Json; }; Update: { bar?: string | undefined; baz?: number[] | undefined; id?: undefined; qux?: Json | undefined; }; }; }[T] extends { ...; } ? { ...; }[T]["Insert"] : never)[]”中的以下属性:length、pop、push、concat等29个属性。

解决方法

问题根源是:你定义的DbInsert<T>类型别名是泛型索引访问类型,TypeScript无法将它与Supabasefrom(resourceName).insert()方法期望的具体表Insert类型做精确匹配,泛型场景下类型推断会出现歧义。

方案1:直接使用表的Insert类型(推荐)

去掉自定义的DbInsert<T>类型别名,直接用DbTables[T]['Insert']作为参数类型,让TypeScript明确泛型与参数的绑定关系:

type DbTables = Database['public']['Tables'];
type TableName = keyof DbTables;

async function create<T extends TableName>(insertData: DbTables[T]['Insert'] | DbTables[T]['Insert'][], resourceName: T) {
  try {
    const { data, error } = await supabase.from(resourceName).insert(insertData).select();
    if (error) throw error;
    return data;
  } catch (error) {
    console.error(error);
  }
}

方案2:保留类型别名并添加约束

如果必须保留DbInsert<T>,可以给它添加条件类型约束,同时通过类型断言消除歧义:

type DbTables = Database['public']['Tables'];
type TableName = keyof DbTables;
// 添加条件类型,明确T是DbTables的有效键
type DbInsert<T extends TableName> = T extends keyof DbTables ? DbTables[T]['Insert'] : never;

async function create<T extends TableName>(insertData: DbInsert<T> | DbInsert<T>[], resourceName: T) {
  try {
    const { data, error } = await supabase.from(resourceName).insert(insertData as DbInsert<T> | DbInsert<T>[]).select();
    if (error) throw error;
    return data;
  } catch (error) {
    console.error(error);
  }
}

原理说明

Supabase的from()方法会根据传入的表名返回对应表的SupabaseQueryBuilder,其insert()方法的参数是该表专属的Insert类型。直接使用DbTables[T]['Insert']时,TypeScript能将泛型T和参数类型做精确绑定,彻底消除类型歧义。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:25:18