metal-orm 1.1.26 → 1.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,131 +1,120 @@
1
- import { BaseSchemaDialect } from './base-schema-dialect.js';
2
1
  import { deriveIndexName } from '../naming-strategy.js';
3
- import { renderIndexColumns, resolvePrimaryKey, createLiteralFormatter } from '../sql-writing.js';
4
- import { ColumnDef, normalizeColumnType, renderTypeWithArgs } from '../../../schema/column-types.js';
5
- import { IndexDef, TableDef } from '../../../schema/table.js';
6
- import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
7
- import { DialectName } from '../schema-dialect.js';
8
-
9
- /** SQLite schema dialect implementation. */
10
- export class SQLiteSchemaDialect extends BaseSchemaDialect {
11
- name: DialectName = 'sqlite';
12
-
13
- private _literalFormatter = createLiteralFormatter({
14
- booleanTrue: '1',
15
- booleanFalse: '0',
16
- });
17
-
18
- get literalFormatter() {
19
- return this._literalFormatter;
20
- }
21
-
22
- quoteIdentifier(id: string): string {
23
- return `"${id}"`;
24
- }
25
-
26
- renderColumnType(column: ColumnDef): string {
27
- const override = column.dialectTypes?.[this.name] ?? column.dialectTypes?.default;
28
- if (override) {
29
- return renderTypeWithArgs(override, column.args);
2
+ import {
3
+ createLiteralFormatter,
4
+ renderIndexColumns,
5
+ resolvePrimaryKey
6
+ } from '../sql-writing.js';
7
+ import {
8
+ composeSchemaDialect,
9
+ createStandardDropTableCapability,
10
+ type SchemaDialectServices
11
+ } from '../schema-dialect-composer.js';
12
+ import type { SchemaDialect } from '../schema-dialect.js';
13
+ import {
14
+ normalizeColumnType,
15
+ renderTypeWithArgs,
16
+ type ColumnDef
17
+ } from '../../../schema/column-types.js';
18
+ import type { IndexDef, TableDef } from '../../../schema/table.js';
19
+ import type { DatabaseTable } from '../schema-types.js';
20
+
21
+ const quoteIdentifier = (id: string): string => `"${id}"`;
22
+ const literalFormatter = createLiteralFormatter({ booleanTrue: '1', booleanFalse: '0' });
23
+
24
+ const renderSqliteColumnType = (
25
+ column: ColumnDef,
26
+ services: SchemaDialectServices
27
+ ): string => {
28
+ const override = column.dialectTypes?.[services.name] ?? column.dialectTypes?.default;
29
+ if (override) return renderTypeWithArgs(override, column.args);
30
+
31
+ const type = normalizeColumnType(column.type);
32
+ switch (type) {
33
+ case 'int':
34
+ case 'integer':
35
+ case 'bigint':
36
+ case 'boolean': return 'INTEGER';
37
+ case 'decimal':
38
+ case 'float':
39
+ case 'double': return 'REAL';
40
+ case 'date':
41
+ case 'datetime':
42
+ case 'timestamp':
43
+ case 'timestamptz':
44
+ case 'varchar':
45
+ case 'text':
46
+ case 'json':
47
+ case 'uuid':
48
+ case 'enum': return 'TEXT';
49
+ case 'binary':
50
+ case 'varbinary':
51
+ case 'blob':
52
+ case 'bytea': return 'BLOB';
53
+ case 'halfvec': {
54
+ const dimensions = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
55
+ return `float16[${dimensions}]`;
30
56
  }
31
-
32
- const type = normalizeColumnType(column.type);
33
- switch (type) {
34
- case 'int':
35
- case 'integer':
36
- case 'bigint':
37
- return 'INTEGER';
38
- case 'boolean':
39
- return 'INTEGER';
40
- case 'decimal':
41
- case 'float':
42
- case 'double':
43
- return 'REAL';
44
- case 'date':
45
- case 'datetime':
46
- case 'timestamp':
47
- case 'timestamptz':
48
- return 'TEXT';
49
- case 'varchar':
50
- case 'text':
51
- case 'json':
52
- case 'uuid':
53
- case 'enum':
54
- return 'TEXT';
55
- case 'binary':
56
- case 'varbinary':
57
- case 'blob':
58
- case 'bytea':
59
- return 'BLOB';
60
- case 'halfvec': {
61
- const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
62
- return `float16[${dim}]`;
63
- }
64
- case 'vector': {
65
- const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
66
- const elemType = column.vectorOptions?.elementType === 'float16' ? 'float16' : 'float32';
67
- return `${elemType}[${dim}]`;
68
- }
69
- default:
70
- return 'TEXT';
71
- }
72
- }
73
-
74
- renderAutoIncrement(column: ColumnDef, table: TableDef): string | undefined {
75
- const pk = resolvePrimaryKey(table);
76
- if (column.autoIncrement && pk.length === 1 && pk[0] === column.name) {
77
- return 'PRIMARY KEY AUTOINCREMENT';
57
+ case 'vector': {
58
+ const dimensions = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
59
+ const elementType = column.vectorOptions?.elementType === 'float16' ? 'float16' : 'float32';
60
+ return `${elementType}[${dimensions}]`;
78
61
  }
79
- return undefined;
80
- }
81
-
82
- preferInlinePkAutoincrement(column: ColumnDef, table: TableDef, pk: string[]): boolean {
83
- return !!(column.autoIncrement && pk.length === 1 && pk[0] === column.name);
84
- }
85
-
86
- renderDefault(value: unknown): string {
87
- return this.literalFormatter.formatLiteral(value);
88
- }
89
-
90
- renderIndex(table: TableDef, index: IndexDef): string {
91
- if (index.where) {
92
- throw new Error('SQLite does not support partial/filtered indexes');
93
- }
94
- const name = index.name || deriveIndexName(table, index);
95
- const cols = renderIndexColumns(this, index.columns);
96
- const unique = index.unique ? 'UNIQUE ' : '';
97
- return `CREATE ${unique}INDEX IF NOT EXISTS ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)} (${cols});`;
98
- }
99
-
100
- dropColumnSql(_table: DatabaseTable, _column: string): string[] {
101
- void _table;
102
- void _column;
103
- return [];
62
+ default: return 'TEXT';
104
63
  }
64
+ };
65
+
66
+ export const createSqliteSchemaDialect = (): SchemaDialect =>
67
+ composeSchemaDialect({
68
+ name: 'sqlite',
69
+ quoteIdentifier,
70
+ literalFormatter,
71
+ renderColumnType: renderSqliteColumnType,
72
+ renderAutoIncrement(column, table) {
73
+ const primaryKey = resolvePrimaryKey(table);
74
+ return column.autoIncrement && primaryKey.length === 1 && primaryKey[0] === column.name
75
+ ? 'PRIMARY KEY AUTOINCREMENT'
76
+ : undefined;
77
+ },
78
+ preferInlinePkAutoincrement: (column, table, primaryKey) => {
79
+ void table;
80
+ return !!(column.autoIncrement && primaryKey.length === 1 && primaryKey[0] === column.name);
81
+ },
82
+ renderIndex(table, index, services) {
83
+ const name = index.name || deriveIndexName(table, index);
84
+ const columns = renderIndexColumns(services, index.columns);
85
+ const unique = index.unique ? 'UNIQUE ' : '';
86
+ const where = index.where ? ` WHERE ${index.where}` : '';
87
+ return `CREATE ${unique}INDEX IF NOT EXISTS ${services.quoteIdentifier(name)} ON ${services.formatTableName(table)} (${columns})${where};`;
88
+ },
89
+ supportsPartialIndexes: true,
90
+ mutations: services => ({
91
+ dropTable: createStandardDropTableCapability(services),
92
+ dropIndex: {
93
+ compile: (_table, index) => [`DROP INDEX IF EXISTS ${services.quoteIdentifier(index)};`]
94
+ }
95
+ })
96
+ });
105
97
 
106
- dropIndexSql(_table: DatabaseTable, index: string): string[] {
107
- void _table;
108
- return [`DROP INDEX IF EXISTS ${this.quoteIdentifier(index)};`];
109
- }
98
+ /** Ergonomic facade; DDL rendering itself is pure composition. */
99
+ export class SQLiteSchemaDialect implements SchemaDialect {
100
+ private readonly delegate = createSqliteSchemaDialect();
101
+ readonly name = this.delegate.name;
102
+ readonly mutations = this.delegate.mutations;
110
103
 
111
- warnDropColumn(table: DatabaseTable, column: string): string | undefined {
112
- const key = table.schema ? `${table.schema}.${table.name}` : table.name;
113
- return `Dropping columns on SQLite requires table rebuild (column ${column} on ${key}).`;
104
+ quoteIdentifier(id: string): string { return this.delegate.quoteIdentifier(id); }
105
+ formatTableName(table: TableDef | DatabaseTable): string { return this.delegate.formatTableName(table); }
106
+ renderColumnType(column: ColumnDef): string { return this.delegate.renderColumnType(column); }
107
+ renderDefault(value: unknown, column: ColumnDef): string { return this.delegate.renderDefault(value, column); }
108
+ renderAutoIncrement(column: ColumnDef, table: TableDef): string | undefined {
109
+ return this.delegate.renderAutoIncrement(column, table);
114
110
  }
115
-
116
- alterColumnSql(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
117
- void _table;
118
- void _column;
119
- void _actual;
120
- void _diff;
121
- return [];
111
+ renderReference(ref: Parameters<SchemaDialect['renderReference']>[0], table: TableDef): string {
112
+ return this.delegate.renderReference(ref, table);
122
113
  }
123
-
124
- warnAlterColumn(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
125
- void _actual;
126
- void _diff;
127
- const key = table.schema ? `${table.schema}.${table.name}` : table.name;
128
- return `SQLite ALTER COLUMN is not supported; rebuild table ${key} to change column ${column.name}.`;
114
+ renderIndex(table: TableDef, index: IndexDef): string { return this.delegate.renderIndex(table, index); }
115
+ renderTableOptions(table: TableDef): string | undefined { return this.delegate.renderTableOptions(table); }
116
+ supportsPartialIndexes(): boolean { return this.delegate.supportsPartialIndexes(); }
117
+ preferInlinePkAutoincrement(column: ColumnDef, table: TableDef, pk: string[]): boolean {
118
+ return this.delegate.preferInlinePkAutoincrement(column, table, pk);
129
119
  }
130
120
  }
131
-
@@ -0,0 +1,129 @@
1
+ import type { TableDef, IndexDef } from '../../schema/table.js';
2
+ import type { ColumnDef, ForeignKeyReference } from '../../schema/column-types.js';
3
+ import type { DatabaseTable } from './schema-types.js';
4
+ import type {
5
+ DialectName,
6
+ SchemaDialect,
7
+ SchemaMutationCapabilities
8
+ } from './schema-dialect.js';
9
+ import {
10
+ formatLiteral,
11
+ quoteQualified,
12
+ type LiteralFormatter
13
+ } from './sql-writing.js';
14
+
15
+ export interface SchemaDialectServices {
16
+ readonly name: DialectName;
17
+ quoteIdentifier(id: string): string;
18
+ formatTableName(table: TableDef | DatabaseTable): string;
19
+ renderDefault(value: unknown, column: ColumnDef): string;
20
+ }
21
+
22
+ export interface SchemaDialectConfig {
23
+ name: DialectName;
24
+ quoteIdentifier(id: string): string;
25
+ literalFormatter: LiteralFormatter;
26
+
27
+ renderColumnType(column: ColumnDef, services: SchemaDialectServices): string;
28
+ renderAutoIncrement(
29
+ column: ColumnDef,
30
+ table: TableDef,
31
+ services: SchemaDialectServices
32
+ ): string | undefined;
33
+ renderIndex(
34
+ table: TableDef,
35
+ index: IndexDef,
36
+ services: SchemaDialectServices
37
+ ): string;
38
+
39
+ renderDefault?(
40
+ value: unknown,
41
+ column: ColumnDef,
42
+ services: SchemaDialectServices
43
+ ): string;
44
+ renderReferenceSuffix?(
45
+ ref: ForeignKeyReference,
46
+ table: TableDef,
47
+ services: SchemaDialectServices
48
+ ): string | undefined;
49
+ renderTableOptions?(
50
+ table: TableDef,
51
+ services: SchemaDialectServices
52
+ ): string | undefined;
53
+ supportsPartialIndexes?: boolean;
54
+ preferInlinePkAutoincrement?(
55
+ column: ColumnDef,
56
+ table: TableDef,
57
+ pk: string[],
58
+ services: SchemaDialectServices
59
+ ): boolean;
60
+ mutations?: (services: SchemaDialectServices) => SchemaMutationCapabilities;
61
+ }
62
+
63
+ /**
64
+ * Assembles a complete schema dialect from independent rendering functions and
65
+ * mutation capabilities. No inheritance participates in the DDL path.
66
+ */
67
+ export const composeSchemaDialect = (config: SchemaDialectConfig): SchemaDialect => {
68
+ const quoteIdentifier = (id: string): string => config.quoteIdentifier(id);
69
+ const formatTableName = (table: TableDef | DatabaseTable): string =>
70
+ table.schema
71
+ ? `${quoteIdentifier(table.schema)}.${quoteIdentifier(table.name)}`
72
+ : quoteIdentifier(table.name);
73
+
74
+ let services!: SchemaDialectServices;
75
+ const renderDefault = (value: unknown, column: ColumnDef): string =>
76
+ config.renderDefault?.(value, column, services)
77
+ ?? formatLiteral(config.literalFormatter, value);
78
+
79
+ services = {
80
+ name: config.name,
81
+ quoteIdentifier,
82
+ formatTableName,
83
+ renderDefault
84
+ };
85
+
86
+ const mutations = config.mutations?.(services) ?? {};
87
+
88
+ return {
89
+ name: config.name,
90
+ mutations,
91
+ quoteIdentifier,
92
+ formatTableName,
93
+ renderColumnType: column => config.renderColumnType(column, services),
94
+ renderDefault,
95
+ renderAutoIncrement: (column, table) =>
96
+ config.renderAutoIncrement(column, table, services),
97
+ renderReference(ref, table) {
98
+ const parts = [
99
+ 'REFERENCES',
100
+ quoteQualified({ quoteIdentifier }, ref.table),
101
+ `(${quoteIdentifier(ref.column)})`
102
+ ];
103
+ if (ref.onDelete) parts.push('ON DELETE', ref.onDelete);
104
+ if (ref.onUpdate) parts.push('ON UPDATE', ref.onUpdate);
105
+ const suffix = config.renderReferenceSuffix?.(ref, table, services);
106
+ if (suffix) parts.push(suffix);
107
+ return parts.join(' ');
108
+ },
109
+ renderIndex: (table, index) => config.renderIndex(table, index, services),
110
+ renderTableOptions: table => config.renderTableOptions?.(table, services),
111
+ supportsPartialIndexes: () => config.supportsPartialIndexes ?? false,
112
+ preferInlinePkAutoincrement: (column, table, pk) =>
113
+ config.preferInlinePkAutoincrement?.(column, table, pk, services) ?? false
114
+ };
115
+ };
116
+
117
+ export const createStandardDropTableCapability = (
118
+ services: SchemaDialectServices
119
+ ): NonNullable<SchemaMutationCapabilities['dropTable']> => ({
120
+ compile: table => [`DROP TABLE IF EXISTS ${services.formatTableName(table)};`]
121
+ });
122
+
123
+ export const createStandardDropColumnCapability = (
124
+ services: SchemaDialectServices
125
+ ): NonNullable<SchemaMutationCapabilities['dropColumn']> => ({
126
+ compile: (table, column) => [
127
+ `ALTER TABLE ${services.formatTableName(table)} DROP COLUMN ${services.quoteIdentifier(column)};`
128
+ ]
129
+ });
@@ -10,47 +10,60 @@ export type DialectName =
10
10
  | 'mssql'
11
11
  | (string & {});
12
12
 
13
- /** Interface for schema dialect implementations that handle database-specific DDL operations. */
13
+ export interface DropTableCapability {
14
+ compile(table: DatabaseTable): string[];
15
+ warning?(table: DatabaseTable): string | undefined;
16
+ }
17
+
18
+ export interface DropColumnCapability {
19
+ compile(table: DatabaseTable, column: string): string[];
20
+ warning?(table: DatabaseTable, column: string): string | undefined;
21
+ }
22
+
23
+ export interface DropIndexCapability {
24
+ compile(table: DatabaseTable, index: string): string[];
25
+ warning?(table: DatabaseTable, index: string): string | undefined;
26
+ }
27
+
28
+ export interface AlterColumnCapability {
29
+ compile(
30
+ table: TableDef,
31
+ column: ColumnDef,
32
+ actualColumn: DatabaseColumn,
33
+ diff: ColumnDiff
34
+ ): string[];
35
+ warning?(
36
+ table: TableDef,
37
+ column: ColumnDef,
38
+ actualColumn: DatabaseColumn,
39
+ diff: ColumnDiff
40
+ ): string | undefined;
41
+ }
42
+
43
+ /** Explicit DDL mutation capabilities supported by a schema dialect. */
44
+ export interface SchemaMutationCapabilities {
45
+ dropTable?: DropTableCapability;
46
+ dropColumn?: DropColumnCapability;
47
+ dropIndex?: DropIndexCapability;
48
+ alterColumn?: AlterColumnCapability;
49
+ }
50
+
51
+ /** Structural contract for database-specific DDL rendering. */
14
52
  export interface SchemaDialect {
15
- /** The name of the dialect. */
16
53
  readonly name: DialectName;
54
+ readonly mutations: SchemaMutationCapabilities;
17
55
 
18
- /** Quotes an identifier for use in SQL. */
19
56
  quoteIdentifier(id: string): string;
20
-
21
- /** Formats the table name for SQL. */
22
57
  formatTableName(table: TableDef | DatabaseTable): string;
23
58
 
24
- /** Renders the column type for SQL. */
25
59
  renderColumnType(column: ColumnDef): string;
26
- /** Renders the default value for SQL. */
27
60
  renderDefault(value: unknown, column: ColumnDef): string;
28
- /** Renders the auto-increment clause for SQL. */
29
61
  renderAutoIncrement(column: ColumnDef, table: TableDef): string | undefined;
30
62
 
31
- /** Renders a foreign key reference for SQL. */
32
63
  renderReference(ref: ForeignKeyReference, table: TableDef): string;
33
- /** Renders an index for SQL. */
34
64
  renderIndex(table: TableDef, index: IndexDef): string;
35
- /** Renders table options for SQL. */
36
65
  renderTableOptions(table: TableDef): string | undefined;
37
66
 
38
- /** Checks if the dialect supports partial indexes. */
39
67
  supportsPartialIndexes(): boolean;
40
- /** Checks if the dialect prefers inline primary key auto-increment. */
41
68
  preferInlinePkAutoincrement(column: ColumnDef, table: TableDef, pk: string[]): boolean;
42
-
43
- /** Generates SQL to drop a column. */
44
- dropColumnSql?(table: DatabaseTable, column: string): string[];
45
- /** Generates SQL to drop an index. */
46
- dropIndexSql?(table: DatabaseTable, index: string): string[];
47
- /** Generates SQL to drop a table. */
48
- dropTableSql?(table: DatabaseTable): string[];
49
- /** Returns a warning message for dropping a column. */
50
- warnDropColumn?(table: DatabaseTable, column: string): string | undefined;
51
- /** Generates SQL to alter a column. */
52
- alterColumnSql?(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[];
53
- /** Returns a warning message for altering a column. */
54
- warnAlterColumn?(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string | undefined;
55
69
  }
56
-