926 lines · 24.0 KB · TypeScript
RawDownload
import { createId } from "../cuid";import type { CustomMigrationFn } from "../migration-engine/create";import type { Condition, ConditionBuilder } from "../query/condition-builder";import { validateSchema } from "./validate"; export type AnySchema = Schema<string, Record<string, AnyTable>>; export type AnyRelation = Relation; export interface AnyTable {  names: NameVariants;  ormName: string;  columns: Record<string, AnyColumn>;  relations: Record<string, AnyRelation>;  foreignKeys: ForeignKey[];  policies: StoredTablePolicy[];  getUniqueConstraints: (level?: "table" | "column" | "all") => UniqueConstraint[];  getColumnByName: (name: string, type?: keyof NameVariants) => AnyColumn | undefined;  getIdColumn: () => AnyColumn;  clone: () => AnyTable;} export type AnyColumn =  | Column<keyof TypeMap, unknown, unknown>  | IdColumn<IdColumnType, unknown, unknown>; export type TableColumnName<T extends AnyTable> = Extract<keyof T["columns"], string>; export type TableColumnValues<T extends AnyTable> = {  [K in keyof T["columns"]]: T["columns"][K]["$out"];}; type Simplify<T> = {  [K in keyof T]: T[K];}; type TableColumnInputValues<T extends AnyTable> = {  [K in keyof T["columns"]]: T["columns"][K]["$in"];}; type PickNotNullable<T> = {  [P in keyof T as null extends T[P] ? never : P]: T[P];}; export type TableInsertValues<T extends AnyTable> = Simplify<  {    [K in keyof T["columns"]]?: T["columns"][K]["$in"];  } & PickNotNullable<TableColumnInputValues<T>>>; export type TableUpdateValues<T extends AnyTable> = {  [K in keyof T["columns"]]?: T["columns"][K] extends IdColumn    ? never    : T["columns"][K]["$in"];}; type MaybePromise<T> = T | Promise<T>;export type TablePolicyCondition = Condition | boolean | void; export interface TableReadPolicyInput<TTable extends AnyTable, TContext> {  readonly where: Condition | undefined;  readonly context: TContext;  readonly builder: ConditionBuilder<TTable["columns"]>;} export interface TableCreatePolicyInput<TTable extends AnyTable, TContext> {  readonly values: TableInsertValues<TTable>;  readonly context: TContext;} export interface TableUpdatePolicyInput<TTable extends AnyTable, TContext> {  readonly where: Condition | undefined;  readonly set: TableUpdateValues<TTable>;  readonly create?: TableInsertValues<TTable>;  readonly context: TContext;  readonly builder: ConditionBuilder<TTable["columns"]>;  readonly operation: "update" | "upsert";} export interface TableDeletePolicyInput<TTable extends AnyTable, TContext> {  readonly where: Condition | undefined;  readonly context: TContext;  readonly builder: ConditionBuilder<TTable["columns"]>;} export type TableReadPolicyCallback<TTable extends AnyTable, TContext> = (  input: TableReadPolicyInput<TTable, TContext>) => MaybePromise<TablePolicyCondition>; export type TableCreatePolicyCallback<TTable extends AnyTable, TContext> = (  input: TableCreatePolicyInput<TTable, TContext>) => MaybePromise<void>; export type TableUpdatePolicyCallback<TTable extends AnyTable, TContext> = (  input: TableUpdatePolicyInput<TTable, TContext>) => MaybePromise<TablePolicyCondition>; export type TableDeletePolicyCallback<TTable extends AnyTable, TContext> = (  input: TableDeletePolicyInput<TTable, TContext>) => MaybePromise<TablePolicyCondition>; export interface TablePolicy<  TTable extends AnyTable = AnyTable,  TContext = unknown,> {  readonly name?: string;  onRead?: TableReadPolicyCallback<TTable, TContext>;  onCreate?: TableCreatePolicyCallback<TTable, TContext>;  onUpdate?: TableUpdatePolicyCallback<TTable, TContext>;  onDelete?: TableDeletePolicyCallback<TTable, TContext>;} export interface StoredTablePolicy {  readonly name?: string;  onRead?: TableReadPolicyCallback<AnyTable, unknown>;  onCreate?: TableCreatePolicyCallback<AnyTable, unknown>;  onUpdate?: TableUpdatePolicyCallback<AnyTable, unknown>;  onDelete?: TableDeletePolicyCallback<AnyTable, unknown>;} export type ForeignKeyAction = "RESTRICT" | "CASCADE" | "SET NULL"; export interface NameVariants {  sql: string;  drizzle: string;  prisma: string;  convex: string;  mongodb: string;} export interface ForeignKey {  name: string;  table: AnyTable;  columns: AnyColumn[];   referencedTable: AnyTable;  referencedColumns: AnyColumn[];  onUpdate: ForeignKeyAction;  onDelete: ForeignKeyAction;} class RelationInit<  Type extends RelationType,  Tables extends Record<string, AnyTable>,  T extends keyof Tables,> {  type: Type;  referencedTable: Tables[T];  referencer: AnyTable;   constructor(type: Type, referencedTable: Tables[T], referencer: AnyTable) {    this.type = type;    this.referencedTable = referencedTable;    this.referencer = referencer;  }} export class ImplicitRelationInit<  Type extends RelationType,  Tables extends Record<string, AnyTable>,  T extends keyof Tables,> extends RelationInit<Type, Tables, T> {  init(ormName: string, impliedBy: ExplicitRelation) {    const output: ImplicitRelation<Type, Tables[T]> = {      id: impliedBy.id,      on: impliedBy.on.map(([left, right]) => [right, left]),      type: this.type,      table: this.referencedTable,      implied: true,      impliedBy,      name: ormName,      referencer: this.referencer,    };     impliedBy.implying = output;    return output;  }} export interface UniqueConstraint {  name: string;  columns: AnyColumn[];} interface ForeignKeyConfig {  name: string;  onUpdate: ForeignKeyAction;  onDelete: ForeignKeyAction;} export class ExplicitRelationInit<  Type extends RelationType,  Tables extends Record<string, AnyTable>,  T extends keyof Tables,> extends RelationInit<Type, Tables, T> {  private foreignKeyConfig?: Partial<ForeignKeyConfig>;  implyingRelationName?: string;  on: [string, string][] = [];   imply(implyingRelationName: string) {    this.implyingRelationName = implyingRelationName;    return this;  }   private initForeignKey(ormName: string): ForeignKey | undefined {    const config = this.foreignKeyConfig;    if (!config) return;     const columns: AnyColumn[] = [];    const referencedColumns: AnyColumn[] = [];     for (const [left, right] of this.on) {      columns.push(this.referencer.columns[left]);      referencedColumns.push(this.referencedTable.columns[right]);    }     return {      columns,      referencedColumns,      referencedTable: this.referencedTable,      table: this.referencer,      name:        config.name ??        `${this.referencer.ormName}_${this.referencedTable.ormName}_${ormName}_fk`,      onDelete: config.onDelete ?? "RESTRICT",      onUpdate: config.onUpdate ?? "RESTRICT",    };  }   init(ormName: string): ExplicitRelation<Type, Tables[T]> {    let id = `${this.referencer.ormName}_${this.referencedTable.ormName}`;    if (this.implyingRelationName) id += `_${this.implyingRelationName}`;     return {      id,      implied: false,      foreignKey: this.initForeignKey(ormName),      implying: undefined,      on: this.on,      name: ormName,      referencer: this.referencer,      table: this.referencedTable,      type: this.type,    };  }   /**   * Define foreign key for explicit relation, please note that:   *   * - this constraint is ignored for MongoDB (without Prisma).   * - you **must** define foreign key for explicit relations, due to the limitations of Prisma.   */  foreignKey(config: Partial<ForeignKeyConfig> = {}) {    this.foreignKeyConfig = config;    return this;  }} interface BaseRelation<  Type extends RelationType = RelationType,  T extends AnyTable = AnyTable,> {  /**   * The relation id shared between implied/implying relation   */  id: string;  name: string;  type: Type;   table: T;  referencer: AnyTable;   on: [string, string][];} export interface ImplicitRelation<  Type extends RelationType = RelationType,  T extends AnyTable = AnyTable,> extends BaseRelation<Type, T> {  implied: true;  readonly impliedBy: ExplicitRelation;} export interface ExplicitRelation<  Type extends RelationType = RelationType,  T extends AnyTable = AnyTable,> extends BaseRelation<Type, T> {  implied: false;  implying: ImplicitRelation | undefined;  foreignKey?: ForeignKey;} export type Relation<  Type extends RelationType = RelationType,  T extends AnyTable = AnyTable,> = ImplicitRelation<Type, T> | ExplicitRelation<Type, T>; export interface Table<  Columns extends Record<string, AnyColumn> = Record<string, AnyColumn>,  Relations extends Record<string, AnyRelation> = Record<string, AnyRelation>,> {  names: NameVariants;  ormName: string;   columns: Columns;  relations: Relations;  foreignKeys: ForeignKey[];  policies: StoredTablePolicy[];   /**   * @param level default to 'all'   */  getUniqueConstraints: (    level?: "table" | "column" | "all"  ) => UniqueConstraint[];   /**   * @param name - name   * @param type - default to "sql"   */  getColumnByName: (    name: string,    type?: keyof NameVariants  ) => AnyColumn | undefined;  getIdColumn: () => AnyColumn;   /**   * Add unique constraint to the fields, for consistency, duplicated null values are allowed.   */  unique(name: string, columns: (keyof Columns)[]): Table<Columns, Relations>;   policy<TContext>(policy: TablePolicy<this, TContext>): this;   clone: () => Table<Columns, Relations>;} type DefaultFunctionMap = {  date: "now";  timestamp: "now";  string: "auto";} & Record<`varchar(${number})`, "auto">; type DefaultFunction<Type extends keyof TypeMap> =  | (Type extends keyof DefaultFunctionMap ? DefaultFunctionMap[Type] : never)  | (() => TypeMap[Type]); type IdColumnType = `varchar(${number})` | "uuid"; export type TypeMap = {  string: string;  bigint: bigint;  integer: number;  decimal: number;  bool: boolean;  json: unknown;  /**   * this follows the same specs as Prisma `Bytes` for consistency.   */  binary: Uint8Array;  date: Date;  timestamp: Date;  uuid: string;} & Record<`varchar(${number})`, string>; export class Column<Type extends keyof TypeMap, In = unknown, Out = unknown> {  type: Type;  ormName: string = "";  isNullable: boolean = false;  isUnique: boolean = false;  default?:    | { value: TypeMap[Type] }    | {        runtime: DefaultFunction<Type>;      };   table: AnyTable = undefined as unknown as AnyTable;   private initNames: (ormName: string) => NameVariants;   get names(): NameVariants {    return this.initNames(this.ormName);  }   set names(v: NameVariants) {    this.initNames = () => v;  }   constructor(type: Type, onInitNames: (ormName: string) => NameVariants) {    this.type = type;    this.initNames = onInitNames;  }   nullable<T extends boolean = true>(nullable?: T) {    this.isNullable = nullable ?? true;     return this as Column<      Type,      T extends true ? In | null : Exclude<In, null>,      T extends true ? Out | null : Exclude<Out, null>    >;  }   /**   * Add unique constraint to the field, for consistency, duplicated null values are allowed.   */  unique(unique: boolean = true) {    this.isUnique = unique;    return this;  }   /**   * Generate default value on runtime   */  defaultTo$(fn: DefaultFunction<Type>): Column<Type, In | null, Out> {    this.default = { runtime: fn };    return this;  }   /**   * Set a database-level default value   *   * For schemaless database, it's still generated on runtime   */  defaultTo(value: TypeMap[Type]): Column<Type, In | null, Out> {    this.default = { value };    return this;  }   clone() {    const clone = new Column(this.type, () => this.names);    clone.ormName = this.ormName;    clone.isNullable = this.isNullable;    clone.isUnique = this.isUnique;    clone.default = this.default;    clone.table = this.table;    return clone;  }   getUniqueConstraintName(): string {    return `unique_c_${this.table.ormName}_${this.ormName}`;  }   /**   * Generate default value for the column on runtime.   */  generateDefaultValue(): TypeMap[Type] | undefined {    if (!this.default) return;     if ("value" in this.default) return this.default.value;    if (this.default.runtime === "auto") return createId() as TypeMap[Type];    if (this.default.runtime === "now")      return new Date(Date.now()) as TypeMap[Type];     return this.default.runtime();  }   get $in(): In {    throw new Error("Type inference only");  }  get $out(): Out {    throw new Error("Type inference only");  }} export class IdColumn<  Type extends IdColumnType = IdColumnType,  In = unknown,  Out = unknown,> extends Column<Type, In, Out> {  id = true;   constructor(type: Type, onInitNames: (ormName: string) => NameVariants) {    super(type, (ormName) => ({      ...onInitNames(ormName),      mongodb: "_id",    }));  }   override clone() {    const clone = new IdColumn(this.type, () => this.names);    clone.ormName = this.ormName;    clone.isNullable = this.isNullable;    clone.isUnique = this.isUnique;    clone.default = this.default;    clone.table = this.table;    return clone;  }   override defaultTo$(fn: DefaultFunction<Type>) {    return super.defaultTo$(fn) as IdColumn<Type, In | null, Out>;  }   override defaultTo(value: TypeMap[Type]) {    return super.defaultTo(value) as IdColumn<Type, In | null, Out>;  }} export function column<Type extends keyof TypeMap>(  name: string | Partial<NameVariants>,  type: Type

Lines 1–500 of 926.