@uql/core 3.7.13 → 3.8.1

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.
Files changed (210) hide show
  1. package/CHANGELOG.md +11 -2
  2. package/README.md +16 -8
  3. package/dist/browser/uql-browser.min.js +16 -15
  4. package/dist/browser/uql-browser.min.js.map +1 -1
  5. package/dist/dialect/abstractDialect.d.ts +7 -2
  6. package/dist/dialect/abstractDialect.d.ts.map +1 -1
  7. package/dist/dialect/abstractDialect.js +9 -1
  8. package/dist/dialect/abstractDialect.js.map +1 -1
  9. package/dist/dialect/abstractSqlDialect.d.ts +5 -6
  10. package/dist/dialect/abstractSqlDialect.d.ts.map +1 -1
  11. package/dist/dialect/abstractSqlDialect.js +14 -13
  12. package/dist/dialect/abstractSqlDialect.js.map +1 -1
  13. package/dist/dialect/dialectConfig.d.ts +17 -0
  14. package/dist/dialect/dialectConfig.d.ts.map +1 -0
  15. package/dist/dialect/dialectConfig.js +71 -0
  16. package/dist/dialect/dialectConfig.js.map +1 -0
  17. package/dist/dialect/index.d.ts +1 -0
  18. package/dist/dialect/index.d.ts.map +1 -1
  19. package/dist/dialect/index.js +1 -0
  20. package/dist/dialect/index.js.map +1 -1
  21. package/dist/entity/decorator/definition.d.ts.map +1 -1
  22. package/dist/entity/decorator/definition.js +2 -1
  23. package/dist/entity/decorator/definition.js.map +1 -1
  24. package/dist/entity/decorator/index-decorator.d.ts +36 -0
  25. package/dist/entity/decorator/index-decorator.d.ts.map +1 -0
  26. package/dist/entity/decorator/index-decorator.js +51 -0
  27. package/dist/entity/decorator/index-decorator.js.map +1 -0
  28. package/dist/entity/decorator/index.d.ts +1 -0
  29. package/dist/entity/decorator/index.d.ts.map +1 -1
  30. package/dist/entity/decorator/index.js +1 -0
  31. package/dist/entity/decorator/index.js.map +1 -1
  32. package/dist/maria/mariaDialect.d.ts +2 -1
  33. package/dist/maria/mariaDialect.d.ts.map +1 -1
  34. package/dist/maria/mariaDialect.js +3 -0
  35. package/dist/maria/mariaDialect.js.map +1 -1
  36. package/dist/migrate/builder/columnBuilder.d.ts +75 -0
  37. package/dist/migrate/builder/columnBuilder.d.ts.map +1 -0
  38. package/dist/migrate/builder/columnBuilder.js +149 -0
  39. package/dist/migrate/builder/columnBuilder.js.map +1 -0
  40. package/dist/migrate/builder/expressions.d.ts +87 -0
  41. package/dist/migrate/builder/expressions.d.ts.map +1 -0
  42. package/dist/migrate/builder/expressions.js +150 -0
  43. package/dist/migrate/builder/expressions.js.map +1 -0
  44. package/dist/migrate/builder/index.d.ts +6 -0
  45. package/dist/migrate/builder/index.d.ts.map +1 -0
  46. package/dist/migrate/builder/index.js +6 -0
  47. package/dist/migrate/builder/index.js.map +1 -0
  48. package/dist/migrate/builder/migrationBuilder.d.ts +73 -0
  49. package/dist/migrate/builder/migrationBuilder.d.ts.map +1 -0
  50. package/dist/migrate/builder/migrationBuilder.js +326 -0
  51. package/dist/migrate/builder/migrationBuilder.js.map +1 -0
  52. package/dist/migrate/builder/tableBuilder.d.ts +51 -0
  53. package/dist/migrate/builder/tableBuilder.d.ts.map +1 -0
  54. package/dist/migrate/builder/tableBuilder.js +278 -0
  55. package/dist/migrate/builder/tableBuilder.js.map +1 -0
  56. package/dist/migrate/builder/types.d.ts +462 -0
  57. package/dist/migrate/builder/types.d.ts.map +1 -0
  58. package/dist/migrate/builder/types.js +8 -0
  59. package/dist/migrate/builder/types.js.map +1 -0
  60. package/dist/migrate/cli.d.ts +4 -6
  61. package/dist/migrate/cli.d.ts.map +1 -1
  62. package/dist/migrate/cli.js +166 -23
  63. package/dist/migrate/cli.js.map +1 -1
  64. package/dist/migrate/codegen/entityCodeGenerator.d.ts +137 -0
  65. package/dist/migrate/codegen/entityCodeGenerator.d.ts.map +1 -0
  66. package/dist/migrate/codegen/entityCodeGenerator.js +401 -0
  67. package/dist/migrate/codegen/entityCodeGenerator.js.map +1 -0
  68. package/dist/migrate/codegen/entityMerger.d.ts +111 -0
  69. package/dist/migrate/codegen/entityMerger.d.ts.map +1 -0
  70. package/dist/migrate/codegen/entityMerger.js +291 -0
  71. package/dist/migrate/codegen/entityMerger.js.map +1 -0
  72. package/dist/migrate/codegen/index.d.ts +10 -0
  73. package/dist/migrate/codegen/index.d.ts.map +1 -0
  74. package/dist/migrate/codegen/index.js +14 -0
  75. package/dist/migrate/codegen/index.js.map +1 -0
  76. package/dist/migrate/codegen/migrationCodeGenerator.d.ts +62 -0
  77. package/dist/migrate/codegen/migrationCodeGenerator.d.ts.map +1 -0
  78. package/dist/migrate/codegen/migrationCodeGenerator.js +356 -0
  79. package/dist/migrate/codegen/migrationCodeGenerator.js.map +1 -0
  80. package/dist/migrate/codegen/smartRelationDetector.d.ts +48 -0
  81. package/dist/migrate/codegen/smartRelationDetector.d.ts.map +1 -0
  82. package/dist/migrate/codegen/smartRelationDetector.js +135 -0
  83. package/dist/migrate/codegen/smartRelationDetector.js.map +1 -0
  84. package/dist/migrate/drift/driftDetector.d.ts +81 -0
  85. package/dist/migrate/drift/driftDetector.d.ts.map +1 -0
  86. package/dist/migrate/drift/driftDetector.js +248 -0
  87. package/dist/migrate/drift/driftDetector.js.map +1 -0
  88. package/dist/migrate/drift/index.d.ts +7 -0
  89. package/dist/migrate/drift/index.d.ts.map +1 -0
  90. package/dist/migrate/drift/index.js +7 -0
  91. package/dist/migrate/drift/index.js.map +1 -0
  92. package/dist/migrate/generator/index.d.ts +1 -3
  93. package/dist/migrate/generator/index.d.ts.map +1 -1
  94. package/dist/migrate/generator/index.js +1 -3
  95. package/dist/migrate/generator/index.js.map +1 -1
  96. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +29 -3
  97. package/dist/migrate/generator/mongoSchemaGenerator.d.ts.map +1 -1
  98. package/dist/migrate/generator/mongoSchemaGenerator.js +86 -4
  99. package/dist/migrate/generator/mongoSchemaGenerator.js.map +1 -1
  100. package/dist/migrate/index.d.ts +7 -8
  101. package/dist/migrate/index.d.ts.map +1 -1
  102. package/dist/migrate/index.js +11 -9
  103. package/dist/migrate/index.js.map +1 -1
  104. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +28 -0
  105. package/dist/migrate/introspection/baseSqlIntrospector.d.ts.map +1 -0
  106. package/dist/migrate/introspection/baseSqlIntrospector.js +135 -0
  107. package/dist/migrate/introspection/baseSqlIntrospector.js.map +1 -0
  108. package/dist/migrate/introspection/mongoIntrospector.d.ts +6 -0
  109. package/dist/migrate/introspection/mongoIntrospector.d.ts.map +1 -1
  110. package/dist/migrate/introspection/mongoIntrospector.js +54 -0
  111. package/dist/migrate/introspection/mongoIntrospector.js.map +1 -1
  112. package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -1
  113. package/dist/migrate/introspection/mysqlIntrospector.d.ts.map +1 -1
  114. package/dist/migrate/introspection/mysqlIntrospector.js +8 -6
  115. package/dist/migrate/introspection/mysqlIntrospector.js.map +1 -1
  116. package/dist/migrate/introspection/postgresIntrospector.d.ts +2 -1
  117. package/dist/migrate/introspection/postgresIntrospector.d.ts.map +1 -1
  118. package/dist/migrate/introspection/postgresIntrospector.js +3 -1
  119. package/dist/migrate/introspection/postgresIntrospector.js.map +1 -1
  120. package/dist/migrate/introspection/sqliteIntrospector.d.ts +2 -2
  121. package/dist/migrate/introspection/sqliteIntrospector.d.ts.map +1 -1
  122. package/dist/migrate/introspection/sqliteIntrospector.js +3 -4
  123. package/dist/migrate/introspection/sqliteIntrospector.js.map +1 -1
  124. package/dist/migrate/migrator.d.ts +29 -0
  125. package/dist/migrate/migrator.d.ts.map +1 -1
  126. package/dist/migrate/migrator.js +31 -23
  127. package/dist/migrate/migrator.js.map +1 -1
  128. package/dist/migrate/schemaGenerator.d.ts +72 -22
  129. package/dist/migrate/schemaGenerator.d.ts.map +1 -1
  130. package/dist/migrate/schemaGenerator.js +385 -153
  131. package/dist/migrate/schemaGenerator.js.map +1 -1
  132. package/dist/migrate/sync/index.d.ts +7 -0
  133. package/dist/migrate/sync/index.d.ts.map +1 -0
  134. package/dist/migrate/sync/index.js +7 -0
  135. package/dist/migrate/sync/index.js.map +1 -0
  136. package/dist/migrate/sync/schemaSync.d.ts +132 -0
  137. package/dist/migrate/sync/schemaSync.d.ts.map +1 -0
  138. package/dist/migrate/sync/schemaSync.js +260 -0
  139. package/dist/migrate/sync/schemaSync.js.map +1 -0
  140. package/dist/mongo/mongoDialect.d.ts +2 -1
  141. package/dist/mongo/mongoDialect.d.ts.map +1 -1
  142. package/dist/mongo/mongoDialect.js +3 -0
  143. package/dist/mongo/mongoDialect.js.map +1 -1
  144. package/dist/mysql/mysqlDialect.d.ts +2 -0
  145. package/dist/mysql/mysqlDialect.d.ts.map +1 -1
  146. package/dist/mysql/mysqlDialect.js +3 -0
  147. package/dist/mysql/mysqlDialect.js.map +1 -1
  148. package/dist/postgres/postgresDialect.js +1 -1
  149. package/dist/postgres/postgresDialect.js.map +1 -1
  150. package/dist/schema/canonicalType.d.ts +42 -0
  151. package/dist/schema/canonicalType.d.ts.map +1 -0
  152. package/dist/schema/canonicalType.js +524 -0
  153. package/dist/schema/canonicalType.js.map +1 -0
  154. package/dist/schema/index.d.ts +28 -0
  155. package/dist/schema/index.d.ts.map +1 -0
  156. package/dist/schema/index.js +29 -0
  157. package/dist/schema/index.js.map +1 -0
  158. package/dist/schema/schemaAST.d.ts +155 -0
  159. package/dist/schema/schemaAST.d.ts.map +1 -0
  160. package/dist/schema/schemaAST.js +496 -0
  161. package/dist/schema/schemaAST.js.map +1 -0
  162. package/dist/schema/schemaASTBuilder.d.ts +58 -0
  163. package/dist/schema/schemaASTBuilder.d.ts.map +1 -0
  164. package/dist/schema/schemaASTBuilder.js +193 -0
  165. package/dist/schema/schemaASTBuilder.js.map +1 -0
  166. package/dist/schema/schemaASTDiffer.d.ts +84 -0
  167. package/dist/schema/schemaASTDiffer.d.ts.map +1 -0
  168. package/dist/schema/schemaASTDiffer.js +431 -0
  169. package/dist/schema/schemaASTDiffer.js.map +1 -0
  170. package/dist/schema/types.d.ts +347 -0
  171. package/dist/schema/types.d.ts.map +1 -0
  172. package/dist/schema/types.js +11 -0
  173. package/dist/schema/types.js.map +1 -0
  174. package/dist/sqlite/sqliteDialect.js +1 -1
  175. package/dist/sqlite/sqliteDialect.js.map +1 -1
  176. package/dist/sqlite/sqliteQuerierPool.js.map +1 -1
  177. package/dist/type/config.d.ts +6 -0
  178. package/dist/type/config.d.ts.map +1 -1
  179. package/dist/type/entity.d.ts +24 -0
  180. package/dist/type/entity.d.ts.map +1 -1
  181. package/dist/type/migration.d.ts +50 -4
  182. package/dist/type/migration.d.ts.map +1 -1
  183. package/dist/util/field.util.d.ts +0 -1
  184. package/dist/util/field.util.d.ts.map +1 -1
  185. package/dist/util/field.util.js +8 -2
  186. package/dist/util/field.util.js.map +1 -1
  187. package/dist/util/logger.d.ts.map +1 -1
  188. package/dist/util/logger.js +2 -1
  189. package/dist/util/logger.js.map +1 -1
  190. package/dist/util/string.util.d.ts +24 -0
  191. package/dist/util/string.util.d.ts.map +1 -1
  192. package/dist/util/string.util.js +57 -0
  193. package/dist/util/string.util.js.map +1 -1
  194. package/package.json +2 -2
  195. package/dist/migrate/generator/mysqlSchemaGenerator.d.ts +0 -15
  196. package/dist/migrate/generator/mysqlSchemaGenerator.d.ts.map +0 -1
  197. package/dist/migrate/generator/mysqlSchemaGenerator.js +0 -88
  198. package/dist/migrate/generator/mysqlSchemaGenerator.js.map +0 -1
  199. package/dist/migrate/generator/postgresSchemaGenerator.d.ts +0 -19
  200. package/dist/migrate/generator/postgresSchemaGenerator.d.ts.map +0 -1
  201. package/dist/migrate/generator/postgresSchemaGenerator.js +0 -115
  202. package/dist/migrate/generator/postgresSchemaGenerator.js.map +0 -1
  203. package/dist/migrate/generator/sqliteSchemaGenerator.d.ts +0 -16
  204. package/dist/migrate/generator/sqliteSchemaGenerator.d.ts.map +0 -1
  205. package/dist/migrate/generator/sqliteSchemaGenerator.js +0 -74
  206. package/dist/migrate/generator/sqliteSchemaGenerator.js.map +0 -1
  207. package/dist/migrate/type.d.ts +0 -2
  208. package/dist/migrate/type.d.ts.map +0 -1
  209. package/dist/migrate/type.js +0 -2
  210. package/dist/migrate/type.js.map +0 -1
@@ -1,27 +1,54 @@
1
1
  import { AbstractDialect } from '../dialect/index.js';
2
2
  import { getMeta } from '../entity/index.js';
3
- import { escapeSqlId, getKeys, isAutoIncrement, isNumericType } from '../util/index.js';
3
+ import { areTypesEqual, canonicalToSql, fieldOptionsToCanonical, sqlToCanonical } from '../schema/canonicalType.js';
4
+ import { escapeSqlId, getKeys, isAutoIncrement } from '../util/index.js';
5
+ import { formatDefaultValue } from './builder/expressions.js';
4
6
  /**
5
- * Abstract base class for SQL schema generation
7
+ * Unified SQL schema generator.
8
+ * Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
6
9
  */
7
- export class AbstractSchemaGenerator extends AbstractDialect {
8
- escapeIdChar;
9
- constructor(namingStrategy, escapeIdChar = '`') {
10
- super(namingStrategy);
11
- this.escapeIdChar = escapeIdChar;
12
- }
10
+ export class SqlSchemaGenerator extends AbstractDialect {
13
11
  /**
14
12
  * Escape an identifier (table name, column name, etc.)
15
13
  */
16
14
  escapeId(identifier) {
17
- return escapeSqlId(identifier, this.escapeIdChar);
15
+ return escapeSqlId(identifier, this.config.quoteChar);
18
16
  }
17
+ /**
18
+ * Primary key type for auto-increment integer IDs
19
+ */
20
+ get serialPrimaryKeyType() {
21
+ return this.config.serialPrimaryKey;
22
+ }
23
+ // ============================================================================
24
+ // CanonicalType Integration (Unified Type System)
25
+ // ============================================================================
26
+ /**
27
+ * Convert FieldOptions to CanonicalType using the unified type system.
28
+ */
29
+ getCanonicalType(field, fieldType) {
30
+ return fieldOptionsToCanonical(field, fieldType);
31
+ }
32
+ /**
33
+ * Convert CanonicalType to SQL type string for this dialect.
34
+ * Also handles legacy string types for backward compatibility.
35
+ */
36
+ canonicalTypeToSql(type) {
37
+ // Handle legacy string types
38
+ if (typeof type === 'string') {
39
+ return type;
40
+ }
41
+ return canonicalToSql(type, this.dialect);
42
+ }
43
+ // ============================================================================
44
+ // SchemaGenerator Implementation
45
+ // ============================================================================
19
46
  generateCreateTable(entity, options = {}) {
20
47
  const meta = getMeta(entity);
21
48
  const tableName = this.resolveTableName(entity, meta);
22
49
  const columns = this.generateColumnDefinitions(meta);
23
50
  const constraints = this.generateTableConstraints(meta);
24
- const ifNotExists = options.ifNotExists ? 'IF NOT EXISTS ' : '';
51
+ const ifNotExists = options.ifNotExists && this.config.supportsIfNotExists ? 'IF NOT EXISTS ' : '';
25
52
  let sql = `CREATE TABLE ${ifNotExists}${this.escapeId(tableName)} (\n`;
26
53
  sql += columns.map((col) => ` ${col}`).join(',\n');
27
54
  if (constraints.length > 0) {
@@ -99,8 +126,6 @@ export class AbstractSchemaGenerator extends AbstractDialect {
99
126
  statements.push(this.generateDropIndex(diff.tableName, index.name));
100
127
  }
101
128
  }
102
- // Note: columnsToDrop and indexesToDrop cannot be auto-reversed
103
- // because we don't store the original schema. These require manual down migrations.
104
129
  if (diff.columnsToDrop?.length || diff.indexesToDrop?.length) {
105
130
  statements.push(`-- TODO: Manual reversal needed for dropped columns/indexes`);
106
131
  }
@@ -109,9 +134,13 @@ export class AbstractSchemaGenerator extends AbstractDialect {
109
134
  generateCreateIndex(tableName, index) {
110
135
  const unique = index.unique ? 'UNIQUE ' : '';
111
136
  const columns = index.columns.map((c) => this.escapeId(c)).join(', ');
112
- return `CREATE ${unique}INDEX ${this.escapeId(index.name)} ON ${this.escapeId(tableName)} (${columns});`;
137
+ const ifNotExists = this.config.supportsIfNotExists ? 'IF NOT EXISTS ' : '';
138
+ return `CREATE ${unique}INDEX ${ifNotExists}${this.escapeId(index.name)} ON ${this.escapeId(tableName)} (${columns});`;
113
139
  }
114
140
  generateDropIndex(tableName, indexName) {
141
+ if (this.dialect === 'mysql' || this.dialect === 'mariadb') {
142
+ return `DROP INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)};`;
143
+ }
115
144
  return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
116
145
  }
117
146
  /**
@@ -123,7 +152,7 @@ export class AbstractSchemaGenerator extends AbstractDialect {
123
152
  for (const key of fieldKeys) {
124
153
  const field = meta.fields[key];
125
154
  if (field?.virtual)
126
- continue; // Skip virtual fields
155
+ continue;
127
156
  const colDef = this.generateColumnDefinition(key, field, meta);
128
157
  columns.push(colDef);
129
158
  }
@@ -134,53 +163,30 @@ export class AbstractSchemaGenerator extends AbstractDialect {
134
163
  */
135
164
  generateColumnDefinition(fieldKey, field, meta) {
136
165
  const column = this.fieldToColumnSchema(fieldKey, field, meta);
137
- const tableName = this.resolveTableName(meta.entity, meta);
138
- const sqlType = column.isAutoIncrement ? this.serialPrimaryKeyType : column.type;
139
- let definition = `${this.escapeId(column.name)} ${sqlType}`;
140
- // PRIMARY KEY constraint (for non-serial types)
141
- if (column.isPrimaryKey && !sqlType.includes('PRIMARY KEY')) {
142
- definition += ' PRIMARY KEY';
143
- }
144
- // NULL/NOT NULL and UNIQUE
145
- if (!column.isPrimaryKey) {
146
- if (!column.nullable)
147
- definition += ' NOT NULL';
148
- if (column.isUnique)
149
- definition += ' UNIQUE';
150
- }
151
- // DEFAULT value
152
- if (column.defaultValue !== undefined) {
153
- definition += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
154
- }
155
- // COMMENT (if supported)
156
- if (column.comment) {
157
- definition += this.generateColumnComment(tableName, column.name, column.comment);
158
- }
159
- return definition;
166
+ return this.generateColumnDefinitionFromSchema(column);
160
167
  }
161
168
  /**
162
169
  * Generate column definition from a ColumnSchema object
163
170
  */
164
171
  generateColumnDefinitionFromSchema(column, options = {}) {
165
172
  const { includePrimaryKey = true, includeUnique = true } = options;
166
- const isAutoIncrement = column.isAutoIncrement && !column.type.includes('IDENTITY');
167
- let type = isAutoIncrement && this.serialPrimaryKeyType ? this.serialPrimaryKeyType : column.type;
168
- // Some serial types (e.g. Postgres IDENTITY or MySQL AUTO_INCREMENT) include "PRIMARY KEY"
169
- // in their definition string. We strip it if includePrimaryKey is false to avoid double PK errors.
170
- if (!includePrimaryKey) {
171
- type = type.replace(/\s+PRIMARY\s+KEY/i, '');
172
- }
173
- if (column.length && !type.includes('(')) {
174
- type = `${type}(${column.length})`;
175
- }
176
- else if (column.precision !== undefined && !type.includes('(')) {
177
- if (column.scale !== undefined) {
178
- type = `${type}(${column.precision}, ${column.scale})`;
173
+ let type = column.type;
174
+ if (!type.includes('(')) {
175
+ if (column.precision !== undefined) {
176
+ if (column.scale !== undefined) {
177
+ type += `(${column.precision}, ${column.scale})`;
178
+ }
179
+ else {
180
+ type += `(${column.precision})`;
181
+ }
179
182
  }
180
- else {
181
- type = `${type}(${column.precision})`;
183
+ else if (column.length !== undefined) {
184
+ type += `(${column.length})`;
182
185
  }
183
186
  }
187
+ if (!includePrimaryKey) {
188
+ type = type.replace(/\s+PRIMARY\s+KEY/i, '');
189
+ }
184
190
  let definition = `${this.escapeId(column.name)} ${type}`;
185
191
  if (includePrimaryKey && column.isPrimaryKey && !type.includes('PRIMARY KEY')) {
186
192
  definition += ' PRIMARY KEY';
@@ -194,6 +200,9 @@ export class AbstractSchemaGenerator extends AbstractDialect {
194
200
  if (column.defaultValue !== undefined) {
195
201
  definition += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
196
202
  }
203
+ if (column.comment) {
204
+ definition += this.generateColumnComment(column.name, column.comment);
205
+ }
197
206
  return definition;
198
207
  }
199
208
  /**
@@ -215,8 +224,9 @@ export class AbstractSchemaGenerator extends AbstractDialect {
215
224
  // Generate foreign key constraints from references
216
225
  for (const key of fieldKeys) {
217
226
  const field = meta.fields[key];
218
- if (field?.reference && field.foreignKey !== false) {
219
- const refEntity = field.reference();
227
+ const reference = field?.references ?? field?.reference;
228
+ if (reference && field.foreignKey !== false) {
229
+ const refEntity = reference();
220
230
  const refMeta = getMeta(refEntity);
221
231
  const refIdField = refMeta.fields[refMeta.id];
222
232
  const columnName = this.resolveColumnName(key, field);
@@ -230,73 +240,92 @@ export class AbstractSchemaGenerator extends AbstractDialect {
230
240
  return constraints;
231
241
  }
232
242
  getSqlType(field, fieldType) {
233
- // Use explicit column type if specified
234
- if (field.columnType) {
235
- return this.mapColumnType(field.columnType, field);
236
- }
237
- // Inherit type from referenced field if applicable
238
- if (field.reference) {
239
- const refEntity = field.reference();
243
+ // If field has a reference, inherit type from the target primary key
244
+ const reference = field.references ?? field.reference;
245
+ if (reference) {
246
+ const refEntity = reference();
240
247
  const refMeta = getMeta(refEntity);
241
248
  const refIdField = refMeta.fields[refMeta.id];
242
- // Recursively call getSqlType for the referenced ID field
243
- return this.getSqlType({ ...refIdField, reference: undefined }, refIdField.type);
249
+ return this.getSqlType({ ...refIdField, references: undefined, reference: undefined, isId: undefined, autoIncrement: false }, refIdField.type);
244
250
  }
245
- // Priority: 1. field.type (explicit logical type)
246
- // 2. fieldType (inferred TS type)
247
- const type = field.type ?? fieldType;
248
- // Handle semantic types (e.g. 'uuid', 'json', 'vector') or any ColumnType string
249
- if (typeof type === 'string') {
250
- return this.mapColumnType(type, field);
251
+ // Get canonical type and convert to SQL
252
+ const canonical = this.getCanonicalType(field, fieldType);
253
+ // Special case for serial primary keys
254
+ if (isAutoIncrement(field, field.isId === true)) {
255
+ return this.serialPrimaryKeyType;
251
256
  }
252
- if (isNumericType(type)) {
253
- return field.precision ? this.mapColumnType('decimal', field) : this.mapColumnType('bigint', field);
254
- }
255
- if (type === String) {
256
- return this.mapColumnType('varchar', field);
257
- }
258
- if (type === Boolean) {
259
- return this.getBooleanType();
260
- }
261
- if (type === Date) {
262
- return this.mapColumnType('timestamp', field);
257
+ return this.canonicalTypeToSql(canonical);
258
+ }
259
+ /**
260
+ * Get the boolean type for this database
261
+ */
262
+ getBooleanType() {
263
+ return this.canonicalTypeToSql({ category: 'boolean' });
264
+ }
265
+ /**
266
+ * Generate ALTER COLUMN statements (database-specific)
267
+ */
268
+ generateAlterColumnStatements(tableName, column, newDefinition) {
269
+ const table = this.escapeId(tableName);
270
+ const colName = this.escapeId(column.name);
271
+ if (this.config.alterColumnSyntax === 'none') {
272
+ throw new Error(`${this.dialect}: Cannot alter column "${column.name}" - you must recreate the table. ` +
273
+ `This database does not support ALTER COLUMN.`);
274
+ }
275
+ if (this.dialect === 'postgres') {
276
+ const statements = [];
277
+ // PostgreSQL uses separate ALTER COLUMN clauses for different changes
278
+ // 1. Change type
279
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} TYPE ${column.type};`);
280
+ // 2. Change nullability
281
+ if (column.nullable) {
282
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP NOT NULL;`);
283
+ }
284
+ else {
285
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET NOT NULL;`);
286
+ }
287
+ // 3. Change default value
288
+ if (column.defaultValue !== undefined) {
289
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET DEFAULT ${this.formatDefaultValue(column.defaultValue)};`);
290
+ }
291
+ else {
292
+ statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP DEFAULT;`);
293
+ }
294
+ return statements;
263
295
  }
264
- return this.mapColumnType('varchar', field);
296
+ return [`ALTER TABLE ${table} ${this.config.alterColumnSyntax} ${newDefinition};`];
265
297
  }
266
298
  /**
267
299
  * Get table options (e.g., ENGINE for MySQL)
268
300
  */
269
301
  getTableOptions(meta) {
302
+ return this.config.tableOptions ? ` ${this.config.tableOptions}` : '';
303
+ }
304
+ /**
305
+ * Generate column comment clause (if supported)
306
+ */
307
+ generateColumnComment(columnName, comment) {
308
+ if (this.dialect === 'mysql' || this.dialect === 'mariadb') {
309
+ const escapedComment = comment.replace(/'/g, "''");
310
+ return ` COMMENT '${escapedComment}'`;
311
+ }
270
312
  return '';
271
313
  }
272
314
  /**
273
315
  * Format a default value for SQL
274
316
  */
275
317
  formatDefaultValue(value) {
276
- if (value === null) {
277
- return 'NULL';
278
- }
279
- if (typeof value === 'string') {
280
- return `'${value.replace(/'/g, "''")}'`;
318
+ if (this.dialect === 'sqlite' && typeof value === 'boolean') {
319
+ return value ? '1' : '0';
281
320
  }
282
- if (typeof value === 'boolean') {
283
- return value ? 'TRUE' : 'FALSE';
284
- }
285
- if (typeof value === 'number' || typeof value === 'bigint') {
286
- return String(value);
287
- }
288
- if (value instanceof Date) {
289
- return `'${value.toISOString()}'`;
290
- }
291
- return String(value);
321
+ return formatDefaultValue(value);
292
322
  }
293
323
  /**
294
- * Compare two schemas and return the differences
324
+ * Compare an entity with a database table node and return the differences.
295
325
  */
296
- diffSchema(entity, currentSchema) {
326
+ diffSchema(entity, currentTable) {
297
327
  const meta = getMeta(entity);
298
- if (!currentSchema) {
299
- // Table doesn't exist, need to create
328
+ if (!currentTable) {
300
329
  return {
301
330
  tableName: this.resolveTableName(entity, meta),
302
331
  type: 'create',
@@ -305,9 +334,8 @@ export class AbstractSchemaGenerator extends AbstractDialect {
305
334
  const columnsToAdd = [];
306
335
  const columnsToAlter = [];
307
336
  const columnsToDrop = [];
308
- const currentColumns = new Map(currentSchema.columns.map((c) => [c.name, c]));
337
+ const currentColumns = new Map(currentTable.columns);
309
338
  const fieldKeys = getKeys(meta.fields);
310
- // Check for new or altered columns
311
339
  for (const key of fieldKeys) {
312
340
  const field = meta.fields[key];
313
341
  if (field?.virtual)
@@ -315,24 +343,22 @@ export class AbstractSchemaGenerator extends AbstractDialect {
315
343
  const columnName = this.resolveColumnName(key, field);
316
344
  const currentColumn = currentColumns.get(columnName);
317
345
  if (!currentColumn) {
318
- // Column needs to be added
319
346
  columnsToAdd.push(this.fieldToColumnSchema(key, field, meta));
320
347
  }
321
348
  else {
322
- // Check if column needs alteration
323
349
  const desiredColumn = this.fieldToColumnSchema(key, field, meta);
324
- if (this.columnsNeedAlteration(currentColumn, desiredColumn)) {
325
- columnsToAlter.push({ from: currentColumn, to: desiredColumn });
350
+ const currentColumnSchema = this.columnNodeToSchema(currentColumn);
351
+ if (this.columnsNeedAlteration(currentColumnSchema, desiredColumn)) {
352
+ columnsToAlter.push({ from: currentColumnSchema, to: desiredColumn });
326
353
  }
327
354
  }
328
355
  currentColumns.delete(columnName);
329
356
  }
330
- // Remaining columns in currentColumns should be dropped
331
357
  for (const [name] of currentColumns) {
332
358
  columnsToDrop.push(name);
333
359
  }
334
360
  if (columnsToAdd.length === 0 && columnsToAlter.length === 0 && columnsToDrop.length === 0) {
335
- return undefined; // No changes needed
361
+ return undefined;
336
362
  }
337
363
  return {
338
364
  tableName: this.resolveTableName(entity, meta),
@@ -342,6 +368,18 @@ export class AbstractSchemaGenerator extends AbstractDialect {
342
368
  columnsToDrop: columnsToDrop.length > 0 ? columnsToDrop : undefined,
343
369
  };
344
370
  }
371
+ columnNodeToSchema(col) {
372
+ return {
373
+ name: col.name,
374
+ type: this.canonicalTypeToSql(col.type),
375
+ nullable: col.nullable,
376
+ defaultValue: col.defaultValue,
377
+ isPrimaryKey: col.isPrimaryKey,
378
+ isAutoIncrement: col.isAutoIncrement,
379
+ isUnique: col.isUnique,
380
+ comment: col.comment,
381
+ };
382
+ }
345
383
  /**
346
384
  * Convert field options to ColumnSchema
347
385
  */
@@ -365,9 +403,6 @@ export class AbstractSchemaGenerator extends AbstractDialect {
365
403
  * Check if two columns differ enough to require alteration
366
404
  */
367
405
  columnsNeedAlteration(current, desired) {
368
- // If both are primary keys, we skip alteration by default.
369
- // Altering primary keys is complex, dangerous, and often requires
370
- // dropping and recreating foreign keys.
371
406
  if (current.isPrimaryKey && desired.isPrimaryKey) {
372
407
  return false;
373
408
  }
@@ -384,48 +419,12 @@ export class AbstractSchemaGenerator extends AbstractDialect {
384
419
  return false;
385
420
  }
386
421
  /**
387
- * Compare two column types for equality, accounting for dialect-specific differences
422
+ * Compare two column types for equality using the canonical type system
388
423
  */
389
424
  isTypeEqual(current, desired) {
390
- const cType = this.normalizeType(current);
391
- const dType = this.normalizeType(desired);
392
- return cType === dType;
393
- }
394
- /**
395
- * Normalize a column type string for comparison
396
- */
397
- normalizeType(column) {
398
- let type = column.type.toLowerCase();
399
- // Remove any extra spaces and standardize common aliases
400
- type = type
401
- .replace(/\s+/g, '')
402
- .replace(/^integer$/, 'int')
403
- .replace(/^boolean$/, 'tinyint(1)') // Common in MySQL
404
- .replace(/^doubleprecision$/, 'double') // Postgres vs MySQL
405
- .replace(/^float8$/, 'double')
406
- .replace(/^float4$/, 'real')
407
- .replace(/^characterany$/, 'varchar')
408
- .replace(/^charactervarying$/, 'varchar')
409
- .replace(/generated(always|bydefault)asidentity$/, '') // Ignore identity keywords
410
- .replace(/unsigned$/, ''); // Ignore unsigned for type comparison
411
- // Strip display width/length for integer types as they are often
412
- // inconsistent between introspection and generation (e.g. bigint(20) vs bigint)
413
- if (type.includes('int')) {
414
- type = type.replace(/\(\d+\)/, '');
415
- }
416
- // Add length if it's not already in the type string
417
- if (column.length && !type.includes('(')) {
418
- type = `${type}(${column.length})`;
419
- }
420
- else if (column.precision !== undefined && !type.includes('(')) {
421
- if (column.scale !== undefined) {
422
- type = `${type}(${column.precision},${column.scale})`;
423
- }
424
- else {
425
- type = `${type}(${column.precision})`;
426
- }
427
- }
428
- return type;
425
+ const typeA = sqlToCanonical(current.type);
426
+ const typeB = sqlToCanonical(desired.type);
427
+ return areTypesEqual(typeA, typeB);
429
428
  }
430
429
  /**
431
430
  * Compare two default values for equality
@@ -439,9 +438,7 @@ export class AbstractSchemaGenerator extends AbstractDialect {
439
438
  if (val === null)
440
439
  return 'null';
441
440
  if (typeof val === 'string') {
442
- // Remove type casts first (common in Postgres like ::text, ::character varying, ::timestamp without time zone)
443
441
  let s = val.replace(/::[a-z_]+(\s+[a-z_]+)*(\[\])?$/i, '');
444
- // Remove surrounding quotes
445
442
  s = s.replace(/^'(.*)'$/, '$1');
446
443
  if (s.toLowerCase() === 'null')
447
444
  return 'null';
@@ -451,5 +448,240 @@ export class AbstractSchemaGenerator extends AbstractDialect {
451
448
  };
452
449
  return normalize(current) === normalize(desired);
453
450
  }
451
+ // ============================================================================
452
+ // SchemaAST Support Methods
453
+ // ============================================================================
454
+ /**
455
+ * Generate CREATE TABLE SQL from a TableNode.
456
+ */
457
+ generateCreateTableFromNode(table, options = {}) {
458
+ const columns = [];
459
+ const constraints = [];
460
+ for (const col of table.columns.values()) {
461
+ const colDef = this.generateColumnFromNode(col);
462
+ columns.push(colDef);
463
+ }
464
+ if (table.primaryKey.length > 1) {
465
+ const pkCols = table.primaryKey.map((c) => this.escapeId(c.name)).join(', ');
466
+ constraints.push(`PRIMARY KEY (${pkCols})`);
467
+ }
468
+ // Add table-level foreign keys if any
469
+ for (const rel of table.outgoingRelations) {
470
+ if (rel.from.columns.length > 0) {
471
+ const fromCols = rel.from.columns.map((c) => this.escapeId(c.name)).join(', ');
472
+ const toCols = rel.to.columns.map((c) => this.escapeId(c.name)).join(', ');
473
+ const constraintName = rel.name ? `CONSTRAINT ${this.escapeId(rel.name)} ` : '';
474
+ constraints.push(`${constraintName}FOREIGN KEY (${fromCols}) REFERENCES ${this.escapeId(rel.to.table.name)} (${toCols})` +
475
+ ` ON DELETE ${rel.onDelete ?? this.defaultForeignKeyAction} ON UPDATE ${rel.onUpdate ?? this.defaultForeignKeyAction}`);
476
+ }
477
+ }
478
+ const ifNotExists = options.ifNotExists && this.config.supportsIfNotExists ? 'IF NOT EXISTS ' : '';
479
+ let sql = `CREATE TABLE ${ifNotExists}${this.escapeId(table.name)} (\n`;
480
+ sql += columns.map((col) => ` ${col}`).join(',\n');
481
+ if (constraints.length > 0) {
482
+ sql += ',\n';
483
+ sql += constraints.map((c) => ` ${c}`).join(',\n');
484
+ }
485
+ sql += '\n)';
486
+ if (this.config.tableOptions) {
487
+ sql += ` ${this.config.tableOptions}`;
488
+ }
489
+ sql += ';';
490
+ // Generate indexes as separate statements
491
+ const indexStatements = table.indexes.map((idx) => this.generateCreateIndexFromNode(idx, options)).join('\n');
492
+ return indexStatements ? `${sql}\n${indexStatements}` : sql;
493
+ }
494
+ /**
495
+ * Generate a column definition from a ColumnNode.
496
+ */
497
+ generateColumnFromNode(col) {
498
+ const colName = this.escapeId(col.name);
499
+ let sqlType = this.canonicalTypeToSql(col.type);
500
+ if (col.isPrimaryKey && col.isAutoIncrement) {
501
+ sqlType = this.serialPrimaryKeyType;
502
+ }
503
+ let def = `${colName} ${sqlType}`;
504
+ if (!col.nullable && !col.isPrimaryKey) {
505
+ def += ' NOT NULL';
506
+ }
507
+ if (col.isPrimaryKey && col.table.primaryKey.length === 1 && !sqlType.includes('PRIMARY KEY')) {
508
+ def += ' PRIMARY KEY';
509
+ }
510
+ if (col.isUnique && !col.isPrimaryKey) {
511
+ def += ' UNIQUE';
512
+ }
513
+ if (col.defaultValue !== undefined) {
514
+ def += ` DEFAULT ${this.formatDefaultValue(col.defaultValue)}`;
515
+ }
516
+ if (col.comment) {
517
+ def += this.generateColumnComment(col.name, col.comment);
518
+ }
519
+ return def;
520
+ }
521
+ /**
522
+ * Generate CREATE INDEX SQL from an IndexNode.
523
+ */
524
+ generateCreateIndexFromNode(index, options = {}) {
525
+ const uniqueStr = index.unique ? 'UNIQUE ' : '';
526
+ const columns = index.columns.map((c) => this.escapeId(c.name)).join(', ');
527
+ const tableName = this.escapeId(index.table.name);
528
+ const ifNotExists = options.ifNotExists && this.config.supportsIfNotExists ? 'IF NOT EXISTS ' : '';
529
+ return `CREATE ${uniqueStr}INDEX ${ifNotExists}${this.escapeId(index.name)} ON ${tableName} (${columns});`;
530
+ }
531
+ /**
532
+ * Generate DROP TABLE SQL from a TableNode.
533
+ */
534
+ generateDropTableFromNode(table, options = {}) {
535
+ const ifExists = options.ifExists ? 'IF EXISTS ' : '';
536
+ return `DROP TABLE ${ifExists}${this.escapeId(table.name)};`;
537
+ }
538
+ // ============================================================================
539
+ // Phase 3: Builder Operation Methods (Moved forward for unification)
540
+ // ============================================================================
541
+ generateCreateTableFromDefinition(table, options = {}) {
542
+ const tableNode = this.tableDefinitionToNode(table);
543
+ return this.generateCreateTableFromNode(tableNode, options);
544
+ }
545
+ generateDropTableSql(tableName, options) {
546
+ const ifExists = options?.ifExists ? 'IF EXISTS ' : '';
547
+ const cascade = options?.cascade ? ' CASCADE' : '';
548
+ return `DROP TABLE ${ifExists}${this.escapeId(tableName)}${cascade};`;
549
+ }
550
+ generateRenameTableSql(oldName, newName) {
551
+ if (this.dialect === 'mysql' || this.dialect === 'mariadb') {
552
+ return `RENAME TABLE ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
553
+ }
554
+ return `ALTER TABLE ${this.escapeId(oldName)} RENAME TO ${this.escapeId(newName)};`;
555
+ }
556
+ generateAddColumnSql(tableName, column) {
557
+ const colSql = this.generateColumnFromNode(this.fullColumnDefinitionToNode(column, tableName));
558
+ return `ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${colSql};`;
559
+ }
560
+ generateAlterColumnSql(tableName, columnName, column) {
561
+ const colSql = this.generateColumnFromNode(this.fullColumnDefinitionToNode(column, tableName));
562
+ return this.generateAlterColumnStatements(tableName, { name: columnName, type: '' }, colSql).join('\n');
563
+ }
564
+ generateDropColumnSql(tableName, columnName) {
565
+ return `ALTER TABLE ${this.escapeId(tableName)} DROP COLUMN ${this.escapeId(columnName)};`;
566
+ }
567
+ generateRenameColumnSql(tableName, oldName, newName) {
568
+ return `ALTER TABLE ${this.escapeId(tableName)} RENAME COLUMN ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
569
+ }
570
+ generateCreateIndexSql(tableName, index) {
571
+ return this.generateCreateIndex(tableName, index);
572
+ }
573
+ generateDropIndexSql(tableName, indexName) {
574
+ return this.generateDropIndex(tableName, indexName);
575
+ }
576
+ generateAddForeignKeySql(tableName, foreignKey) {
577
+ const fkCols = foreignKey.columns.map((c) => this.escapeId(c)).join(', ');
578
+ const refCols = foreignKey.referencesColumns.map((c) => this.escapeId(c)).join(', ');
579
+ const constraintName = foreignKey.name
580
+ ? this.escapeId(foreignKey.name)
581
+ : this.escapeId(`fk_${tableName}_${foreignKey.columns.join('_')}`);
582
+ return (`ALTER TABLE ${this.escapeId(tableName)} ADD CONSTRAINT ${constraintName} ` +
583
+ `FOREIGN KEY (${fkCols}) REFERENCES ${this.escapeId(foreignKey.referencesTable)} (${refCols}) ` +
584
+ `ON DELETE ${foreignKey.onDelete ?? this.defaultForeignKeyAction} ON UPDATE ${foreignKey.onUpdate ?? this.defaultForeignKeyAction};`);
585
+ }
586
+ generateDropForeignKeySql(tableName, constraintName) {
587
+ return `ALTER TABLE ${this.escapeId(tableName)} ${this.config.dropForeignKeySyntax} ${this.escapeId(constraintName)};`;
588
+ }
589
+ tableDefinitionToNode(def) {
590
+ const columns = new Map();
591
+ const pkNodes = [];
592
+ const table = {
593
+ name: def.name,
594
+ columns,
595
+ primaryKey: [], // placeholder
596
+ indexes: [],
597
+ schema: { tables: new Map(), relationships: [], indexes: [] },
598
+ incomingRelations: [],
599
+ outgoingRelations: [],
600
+ comment: def.comment,
601
+ };
602
+ for (const colDef of def.columns) {
603
+ const node = this.fullColumnDefinitionToNode(colDef, def.name);
604
+ node.table = table;
605
+ columns.set(node.name, node);
606
+ if (node.isPrimaryKey) {
607
+ pkNodes.push(node);
608
+ }
609
+ }
610
+ const finalPrimaryKey = def.primaryKey
611
+ ? def.primaryKey.map((name) => columns.get(name)).filter((c) => c !== undefined)
612
+ : pkNodes;
613
+ table.primaryKey = finalPrimaryKey;
614
+ for (const idxDef of def.indexes) {
615
+ const indexNode = {
616
+ name: idxDef.name,
617
+ table,
618
+ columns: idxDef.columns.map((name) => columns.get(name)).filter((c) => c !== undefined),
619
+ unique: idxDef.unique,
620
+ };
621
+ table.indexes.push(indexNode);
622
+ }
623
+ for (const fkDef of def.foreignKeys) {
624
+ const relNode = {
625
+ name: fkDef.name ?? `fk_${def.name}_${fkDef.columns.join('_')}`,
626
+ type: 'ManyToOne', // Builder default
627
+ from: {
628
+ table,
629
+ columns: fkDef.columns.map((name) => columns.get(name)).filter((c) => c !== undefined),
630
+ },
631
+ to: {
632
+ table: { name: fkDef.referencesTable },
633
+ columns: fkDef.referencesColumns.map((name) => ({ name })),
634
+ },
635
+ onDelete: fkDef.onDelete,
636
+ onUpdate: fkDef.onUpdate,
637
+ };
638
+ table.outgoingRelations.push(relNode);
639
+ }
640
+ return table;
641
+ }
642
+ fullColumnDefinitionToNode(col, tableName) {
643
+ return {
644
+ name: col.name,
645
+ type: col.type,
646
+ nullable: col.nullable,
647
+ defaultValue: col.defaultValue,
648
+ isPrimaryKey: col.primaryKey,
649
+ isAutoIncrement: col.autoIncrement,
650
+ isUnique: col.unique,
651
+ comment: col.comment,
652
+ table: { name: tableName },
653
+ referencedBy: [],
654
+ references: col.foreignKey
655
+ ? {
656
+ name: `fk_${tableName}_${col.name}`,
657
+ type: 'ManyToOne',
658
+ from: { table: { name: tableName }, columns: [] },
659
+ to: {
660
+ table: { name: col.foreignKey.table },
661
+ columns: col.foreignKey.columns.map((name) => ({ name })),
662
+ },
663
+ onDelete: col.foreignKey.onDelete,
664
+ onUpdate: col.foreignKey.onUpdate,
665
+ }
666
+ : undefined,
667
+ };
668
+ }
669
+ }
670
+ import { MongoSchemaGenerator } from './generator/mongoSchemaGenerator.js';
671
+ export { MongoSchemaGenerator };
672
+ /**
673
+ * Factory function to create a SchemaGenerator for a specific dialect.
674
+ * Returns undefined for unsupported dialects.
675
+ */
676
+ export function createSchemaGenerator(dialect, namingStrategy, defaultForeignKeyAction) {
677
+ if (dialect === 'mongodb') {
678
+ return new MongoSchemaGenerator(namingStrategy, defaultForeignKeyAction);
679
+ }
680
+ // Check if dialect is supported (has config)
681
+ const supportedDialects = ['postgres', 'mysql', 'mariadb', 'sqlite'];
682
+ if (!supportedDialects.includes(dialect)) {
683
+ return undefined;
684
+ }
685
+ return new SqlSchemaGenerator(dialect, namingStrategy, defaultForeignKeyAction);
454
686
  }
455
687
  //# sourceMappingURL=schemaGenerator.js.map