UNPKG

drizzle-zero

Version:

Generate Zero schemas from Drizzle ORM schemas

485 lines (479 loc) 19.2 kB
import { Table } from 'drizzle-orm'; import { ReadonlyJSONValue, ColumnBuilder, ValueType, TableBuilderWithColumns } from '@rocicorp/zero'; export { ColumnBuilder, ReadonlyJSONValue, TableBuilderWithColumns } from '@rocicorp/zero'; /** * Maps Drizzle data types to their corresponding Zero schema types. * This is a constant mapping that ensures type safety when converting between the two systems. */ declare const drizzleDataTypeToZeroType: { readonly number: "number"; readonly bigint: "number"; readonly boolean: "boolean"; readonly date: "number"; }; /** * Type representation of the Drizzle to Zero type mapping. * Extracts the type information from the drizzleDataTypeToZeroType constant. */ type DrizzleDataTypeToZeroType = typeof drizzleDataTypeToZeroType; /** * Maps Postgres-specific Drizzle column types to their corresponding Zero schema types. * Handles special cases where Postgres types need specific Zero type representations. */ declare const drizzleColumnTypeToZeroType: { readonly PgText: "string"; readonly PgChar: "string"; readonly PgVarchar: "string"; readonly PgUUID: "string"; readonly PgEnumColumn: "string"; readonly PgJsonb: "json"; readonly PgJson: "json"; readonly PgNumeric: "number"; readonly PgDateString: "number"; readonly PgTimestampString: "number"; readonly PgArray: "json"; }; /** * Type representation of the Postgres-specific Drizzle to Zero type mapping. * Extracts the type information from the drizzleColumnTypeToZeroType constant. */ type DrizzleColumnTypeToZeroType = typeof drizzleColumnTypeToZeroType; /** * Maps Zero schema types to their corresponding TypeScript types. */ type ZeroTypeToTypescriptType = { number: number; boolean: boolean; date: string; string: string; json: ReadonlyJSONValue; }; /** * Gets the keys of columns that can be used as indexes. * @template TTable - The table to get index keys from */ type ColumnIndexKeys<TTable extends Table> = { [K in keyof Columns<TTable>]: K; }[keyof Columns<TTable>]; /** * Configuration type for specifying which tables and columns to include in the Zero schema. * @template TDrizzleSchema - The complete Drizzle schema */ type TableColumnsConfig<TDrizzleSchema extends Record<string, unknown>> = Partial<Flatten<{ /** * The columns to include in the Zero schema. */ readonly [K in keyof TDrizzleSchema as TDrizzleSchema[K] extends Table<any> ? K : never]: TDrizzleSchema[K] extends Table<any> ? ColumnsConfig<TDrizzleSchema[K]> : never; }>>; /** * A default config type which includes all tables in the Drizzle schema. * @template TDrizzleSchema - The complete Drizzle schema */ type DefaultTableColumnsConfig<TDrizzleSchema extends Record<string, unknown>> = Flatten<{ readonly [K in keyof TDrizzleSchema as TDrizzleSchema[K] extends Table<any> ? K : never]: TDrizzleSchema[K] extends Table<any> ? DefaultColumnsConfig<TDrizzleSchema[K]> : never; }>; /** * A default config type which includes all columns in a Drizzle table. * @template TTable - The Drizzle table type */ type DefaultColumnsConfig<TTable extends Table> = { readonly [K in ColumnNames<TTable>]: true; }; /** * Gets all columns from a Drizzle table type. * @template TTable The Drizzle table type */ type Columns<TTable extends Table> = TTable['_']['columns']; /** * Gets all column names from a Drizzle table type. * @template TTable The Drizzle table type */ type ColumnNames<TTable extends Table> = keyof Columns<TTable>; /** * Helper type that extracts primary key columns from a table. * @template T The Drizzle table type */ type PrimaryKeyColumns<T extends Table> = { [K in keyof Columns<T>]: Columns<T>[K]['_']['isPrimaryKey'] extends true ? K extends string ? K : never : never; }[keyof Columns<T>]; /** * Finds the primary key(s) from a table. * @template T The Drizzle table type */ type FindPrimaryKeyFromTable<T extends Table> = [ PrimaryKeyColumns<T> ] extends [never] ? [never] : [PrimaryKeyColumns<T>]; /** * Type guard that checks if a type is a Table with a specific name. * @template T The type to check * @template Name The name to check for */ type IsTableWithName<T, Name extends string> = T extends { _: { name: Name; }; } ? T extends Table<any> ? true : false : false; /** * Finds a table in the schema by its name. * @template TDrizzleSchema The complete Drizzle schema * @template Name The name of the table to find */ type FindTableByName<TDrizzleSchema extends Record<string, unknown>, Name extends string> = Extract<{ [P in keyof TDrizzleSchema]: IsTableWithName<TDrizzleSchema[P], Name> extends true ? TDrizzleSchema[P] : never; }[keyof TDrizzleSchema], Table<any>>; /** * Utility type that flattens an object type by removing any intermediate interfaces. * @template T The type to flatten */ type Flatten<T> = { [K in keyof T]: T[K]; } & {}; /** * Represents a column definition from a Drizzle table, filtered by column name. * @template TTable The Drizzle table type * @template K The column name to filter by */ type ColumnDefinition<TTable extends Table, K extends ColumnNames<TTable>> = { [C in keyof Columns<TTable>]: C extends K ? Columns<TTable>[C] : never; }[keyof Columns<TTable>]; /** * The type override for a column. * Used to customize how a Drizzle column is mapped to a Zero schema. * @template TCustomType The TypeScript type that corresponds to the Zero type */ type TypeOverride<TCustomType> = { readonly type: 'string' | 'number' | 'boolean' | 'json'; readonly optional: boolean; readonly customType: TCustomType; readonly kind?: 'enum'; }; /** * Configuration for specifying which columns to include in the Zero schema and how to map them. * @template TTable The Drizzle table type */ type ColumnsConfig<TTable extends Table> = boolean | Partial<{ /** * The columns to include in the Zero schema. * Set to true to use default mapping, or provide a TypeOverride for custom mapping. */ readonly [KColumn in ColumnNames<TTable>]: boolean | ColumnBuilder<TypeOverride<ZeroTypeToTypescriptType[DrizzleDataTypeToZeroType[Columns<TTable>[KColumn]['dataType']]]>>; }>; /** * Maps a Drizzle column type to its corresponding Zero type. */ type ZeroMappedColumnType<TTable extends Table, KColumn extends ColumnNames<TTable>, CD extends ColumnDefinition<TTable, KColumn>['_'] = ColumnDefinition<TTable, KColumn>['_']> = CD extends { columnType: keyof DrizzleColumnTypeToZeroType; } ? DrizzleColumnTypeToZeroType[CD['columnType']] : DrizzleDataTypeToZeroType[CD['dataType']]; /** * Maps a Drizzle column to its corresponding TypeScript type in Zero. * Handles special cases like enums and custom types. */ type ZeroMappedCustomType<TTable extends Table, KColumn extends ColumnNames<TTable>, CD extends ColumnDefinition<TTable, KColumn>['_'] = ColumnDefinition<TTable, KColumn>['_']> = CD extends { columnType: 'PgCustomColumn'; } ? CD['data'] : CD extends { columnType: 'PgEnumColumn'; } ? CD['data'] : CD extends { columnType: 'PgText'; data: string; } ? CD['data'] : CD extends { columnType: 'PgArray'; data: infer TArrayData; } ? TArrayData : CD extends { $type: any; } ? CD['$type'] : ZeroTypeToTypescriptType[ZeroMappedColumnType<TTable, KColumn>]; /** * Defines the structure of a column in the Zero schema. */ type ZeroColumnDefinition<TTable extends Table, KColumn extends ColumnNames<TTable>> = Flatten<{ optional: boolean; type: ValueType; customType: ZeroMappedCustomType<TTable, KColumn>; serverName?: string; }>; /** * Maps the columns configuration to their Zero schema definitions. */ type ZeroColumns<TTable extends Table, TColumnConfig extends ColumnsConfig<TTable> | undefined> = { [KColumn in ColumnNames<TTable>]: KColumn extends keyof TColumnConfig ? TColumnConfig[KColumn & keyof TColumnConfig] extends ColumnBuilder<any> ? TColumnConfig[KColumn & keyof TColumnConfig]['schema'] : ZeroColumnDefinition<TTable, KColumn> : ZeroColumnDefinition<TTable, KColumn>; }; /** * Represents the underlying schema for a Zero table. */ type ZeroTableBuilderSchema<TTableName extends string, TTable extends Table, TColumnConfig extends ColumnsConfig<TTable> | undefined> = Flatten<{ name: TTableName; primaryKey: FindPrimaryKeyFromTable<TTable> extends [never] ? readonly [string, ...string[]] : readonly [string, ...string[]] & FindPrimaryKeyFromTable<TTable>; columns: Flatten<ZeroColumns<TTable, TColumnConfig>>; }>; /** * Represents the complete Zero schema for a Drizzle table. */ type ZeroTableBuilder<TTableName extends string, TTable extends Table, TColumnConfig extends ColumnsConfig<TTable>> = TableBuilderWithColumns<Readonly<ZeroTableBuilderSchema<TTableName, TTable, TColumnConfig>>>; /** * Casing for the Zero table builder. */ type ZeroTableCasing = 'snake_case' | 'camelCase' | undefined; /** * Creates a Zero schema from a Drizzle table definition. * * @returns A Zero schema definition for the table * @throws {Error} If primary key configuration is invalid or column types are unsupported */ declare const createZeroTableBuilder: <TTableName extends string, TTable extends Table, TColumnConfig extends ColumnsConfig<TTable>, TCasing extends ZeroTableCasing = ZeroTableCasing>( /** * The mapped name of the table */ tableName: TTableName, /** * The Drizzle table instance */ table: TTable, /** * Configuration specifying which columns to include and how to map them */ columns?: TColumnConfig, /** * Whether to enable debug mode. */ debug?: boolean, /** * The casing to use for the table name. */ casing?: TCasing) => ZeroTableBuilder<TTableName, TTable, TColumnConfig>; /** * Get the key of a column in the schema from the column name. * @param columnName - The name of the column to get the key for * @param table - The table to get the column key from * @returns The key of the column in the schema */ declare const getDrizzleColumnKeyFromColumnName: ({ columnName, table, }: { columnName: string; table: Table; }) => string; type IsAny<T> = 0 extends 1 & T ? true : false; type SchemaIsAnyError = { __error__: 'The schema passed in to `ZeroCustomType` is `any`. Please make sure to pass in a proper schema type, or check your imports to make sure that Typescript can resolve your schema definition.'; }; /** * Maps a column definition to its Zero type (string, number, boolean, json). */ type DirectZeroType<CD> = CD extends { columnType: keyof DrizzleColumnTypeToZeroType; } ? DrizzleColumnTypeToZeroType[CD['columnType']] : CD extends { dataType: keyof DrizzleDataTypeToZeroType; } ? DrizzleDataTypeToZeroType[CD['dataType']] : never; /** * Maps column types to their default TypeScript types when no custom type is specified. */ type DefaultColumnType<CD> = DirectZeroType<CD> extends keyof ZeroTypeToTypescriptType ? ZeroTypeToTypescriptType[DirectZeroType<CD>] : unknown; /** * Direct extraction of the custom type from Drizzle schema. This falls back * to the default TypeScript type if no custom type is specified. * * @template DrizzleSchema - The Drizzle schema object (typeof drizzleSchema) * @template TableKey - The key of the table in the schema * @template ColumnKey - The key of the column in the table */ type CustomType<DrizzleSchema, TableKey extends string, ColumnKey extends string> = TableKey extends keyof DrizzleSchema ? DrizzleSchema[TableKey] extends Table ? ColumnKey extends keyof DrizzleSchema[TableKey] ? DrizzleSchema[TableKey][ColumnKey] extends { _: infer CD; } ? CD extends { columnType: 'PgCustomColumn'; data: infer TData; } ? TData : CD extends { columnType: 'PgEnumColumn'; data: infer TData; } ? TData : CD extends { columnType: 'PgText'; data: infer TData; } ? TData extends string ? TData : string : CD extends { columnType: 'PgArray'; data: infer TArrayData; } ? TArrayData : CD extends { $type: infer TType; } ? TType : DefaultColumnType<CD> : unknown : unknown : unknown : unknown; /** * Type utility to get the Drizzle custom type for a table and column. * * @template ZeroSchema - The complete Zero schema * @template TableName - The name of the table * @template ColumnName - The name of the column */ type ZeroCustomType<ZeroSchema, TableName extends string, ColumnName extends string> = IsAny<ZeroSchema> extends true ? SchemaIsAnyError : ZeroSchema extends { tables: Record<TableName, { columns: Record<ColumnName, { customType: infer T; }>; }>; } ? T : unknown; /** * Configuration type for many-to-many relationships for a specific table. * @template TDrizzleSchema - The complete Drizzle schema * @template TSourceTableName - The name of the source table */ type ManyTableConfig<TDrizzleSchema extends Record<string, unknown>, TSourceTableName extends keyof TDrizzleSchema & string> = { readonly [TRelationName: string]: readonly [keyof TDrizzleSchema, keyof TDrizzleSchema] | { [K in keyof TDrizzleSchema]: { [L in keyof TDrizzleSchema]: readonly [ { readonly destTable: K; readonly sourceField: ColumnIndexKeys<FindTableByName<TDrizzleSchema, TSourceTableName & string>>[]; readonly destField: ColumnIndexKeys<FindTableByName<TDrizzleSchema, K & string>>[]; }, { readonly destTable: L; readonly sourceField: ColumnIndexKeys<FindTableByName<TDrizzleSchema, K & string>>[]; readonly destField: ColumnIndexKeys<FindTableByName<TDrizzleSchema, L & string>>[]; } ]; }[keyof TDrizzleSchema]; }[keyof TDrizzleSchema]; }; /** * Configuration for many-to-many relationships across all tables. * Organized by source table, with each relationship specifying a tuple of [junction table name, destination table name]. * The junction table and destination table must be different from the source table and each other. */ type ManyConfig<TDrizzleSchema extends Record<string, unknown>> = { readonly [TSourceTableName in keyof TDrizzleSchema & string]?: ManyTableConfig<TDrizzleSchema, TSourceTableName>; }; /** * The mapped Zero schema from a Drizzle schema with version and tables. */ type DrizzleToZeroSchema<TDrizzleSchema extends { [K in string]: unknown; }, TColumnConfig extends TableColumnsConfig<TDrizzleSchema> = DefaultTableColumnsConfig<TDrizzleSchema>> = { readonly tables: { readonly [K in keyof TDrizzleSchema as TDrizzleSchema[K] extends Table<any> ? K : never]: TDrizzleSchema[K] extends Table<any> ? ZeroTableBuilderSchema<K & string, TDrizzleSchema[K], TColumnConfig[K & keyof TColumnConfig]> : never; }; readonly relationships: any; readonly enableLegacyMutators?: boolean; readonly enableLegacyQueries?: boolean; }; /** * Configuration for the Zero schema generator. This defines how your Drizzle ORM schema * is transformed into a Zero schema format, handling both direct relationships and many-to-many relationships. * * This allows you to: * - Select which tables to include in the Zero schema * - Configure column types and transformations * - Define many-to-many relationships through junction tables * * @param schema - The Drizzle schema to create a Zero schema from. This should be your complete Drizzle schema object * containing all your table definitions and relationships. * @param config - Configuration object for the Zero schema generation * @param config.tables - Specify which tables and columns to include in sync * @param config.manyToMany - Optional configuration for many-to-many relationships through junction tables * @param config.casing - The casing to use for the table name. * @param config.debug - Whether to enable debug mode. * * @returns A configuration object for the Zero schema CLI. * * @example * ```typescript * import { integer, pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; * import { relations } from 'drizzle-orm'; * import { drizzleZeroConfig } from 'drizzle-zero'; * * // Define Drizzle schema * const users = pgTable('users', { * id: serial('id').primaryKey(), * name: text('name'), * }); * * const posts = pgTable('posts', { * id: serial('id').primaryKey(), * title: varchar('title'), * authorId: integer('author_id').references(() => users.id), * }); * * const usersRelations = relations(users, ({ one }) => ({ * posts: one(posts, { * fields: [users.id], * references: [posts.authorId], * }), * })); * * // Export the configuration for the Zero schema CLI * export default drizzleZeroConfig( * { users, posts, usersRelations }, * { * tables: { * users: { * id: true, * name: true, * }, * posts: { * id: true, * title: true, * authorId: true, * }, * }, * } * ); * ``` */ declare const drizzleZeroConfig: <const TDrizzleSchema extends { [K in string]: unknown; }, const TColumnConfig extends TableColumnsConfig<TDrizzleSchema> = DefaultTableColumnsConfig<TDrizzleSchema>, const TManyConfig extends ManyConfig<TDrizzleSchema> | undefined = undefined, const TCasing extends ZeroTableCasing = undefined>( /** * The Drizzle schema to create a Zero schema from. */ schema: TDrizzleSchema, /** * The configuration for the Zero schema. * * @param config.tables - The tables to include in the Zero schema. * @param config.many - Configuration for many-to-many relationships. */ config?: { /** * Specify the tables to include in the Zero schema. * This can include type overrides for columns, using `column.json()` for example. * * @example * ```ts * { * user: { * id: true, * name: true, * }, * profile_info: { * id: true, * user_id: true, * metadata: column.json(), * }, * } * ``` */ readonly tables?: TColumnConfig; /** * Configuration for many-to-many relationships. * Organized by source table, with each relationship specifying a tuple of [junction table name, destination table name]. * * @example * ```ts * { * user: { * comments: ['message', 'comment'] * } * } * ``` */ readonly manyToMany?: TManyConfig; /** * The casing to use for the table name. * * @example * ```ts * { casing: 'snake_case' } * ``` */ readonly casing?: TCasing; /** * Whether to enable debug mode. * * @example * ```ts * { debug: true } * ``` */ readonly debug?: boolean; }) => Flatten<DrizzleToZeroSchema<TDrizzleSchema, TColumnConfig>>; export { type ColumnsConfig, type CustomType, type DrizzleToZeroSchema, type ZeroColumns, type ZeroCustomType, type ZeroTableBuilder, type ZeroTableBuilderSchema, type ZeroTableCasing, createZeroTableBuilder, drizzleZeroConfig, getDrizzleColumnKeyFromColumnName };