uql-orm 0.25.1 → 0.26.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 (56) hide show
  1. package/README.md +9 -8
  2. package/dist/browser/uql-browser.min.js.map +1 -1
  3. package/dist/cockroachdb/cockroachDialect.js +1 -1
  4. package/dist/dialect/indexSqlDialect.d.ts +3 -2
  5. package/dist/dialect/indexSqlDialect.js +10 -8
  6. package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -2
  7. package/dist/dialect/mysqlLikeSqlDialect.js +0 -7
  8. package/dist/dialect/pgLikeSqlDialect.js +1 -0
  9. package/dist/maria/mariaDialect.js +1 -1
  10. package/dist/migrate/builder/migrationBuilder.js +1 -1
  11. package/dist/migrate/builder/tableBuilder.js +2 -2
  12. package/dist/migrate/cli.js +4 -6
  13. package/dist/migrate/codegen/entityCodeGenerator.d.ts +5 -0
  14. package/dist/migrate/codegen/entityCodeGenerator.js +22 -25
  15. package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -1
  16. package/dist/migrate/codegen/fieldOptionsSource.js +6 -1
  17. package/dist/migrate/codegen/indexDecoratorSource.d.ts +14 -0
  18. package/dist/migrate/codegen/indexDecoratorSource.js +105 -0
  19. package/dist/migrate/drift/driftDetector.d.ts +5 -50
  20. package/dist/migrate/drift/driftDetector.js +215 -224
  21. package/dist/migrate/drift/index.d.ts +1 -1
  22. package/dist/migrate/drift/index.js +1 -1
  23. package/dist/migrate/generator/definitionToNode.d.ts +9 -0
  24. package/dist/migrate/generator/definitionToNode.js +79 -0
  25. package/dist/migrate/generator/indexNodeToSchema.js +4 -2
  26. package/dist/migrate/generator/mongoSchemaGenerator.js +3 -3
  27. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +3 -0
  28. package/dist/migrate/introspection/baseSqlIntrospector.js +13 -5
  29. package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -0
  30. package/dist/migrate/introspection/mongoIntrospector.js +5 -10
  31. package/dist/migrate/introspection/mysqlIntrospector.js +1 -1
  32. package/dist/migrate/introspection/postgresIntrospector.d.ts +57 -5
  33. package/dist/migrate/introspection/postgresIntrospector.js +93 -10
  34. package/dist/migrate/introspection/sqliteIntrospector.js +1 -1
  35. package/dist/migrate/migrator.js +3 -2
  36. package/dist/migrate/schemaGenerator.d.ts +26 -3
  37. package/dist/migrate/schemaGenerator.js +53 -92
  38. package/dist/schema/index.d.ts +3 -3
  39. package/dist/schema/index.js +2 -2
  40. package/dist/schema/indexColumns.d.ts +10 -0
  41. package/dist/schema/indexColumns.js +11 -0
  42. package/dist/schema/indexDifferences.d.ts +22 -0
  43. package/dist/schema/indexDifferences.js +49 -0
  44. package/dist/schema/schemaAST.js +2 -8
  45. package/dist/schema/schemaASTBuilder.d.ts +6 -59
  46. package/dist/schema/schemaASTBuilder.js +208 -236
  47. package/dist/schema/schemaASTDiffer.d.ts +9 -56
  48. package/dist/schema/schemaASTDiffer.js +229 -393
  49. package/dist/schema/types.d.ts +5 -12
  50. package/dist/schema/types.js +15 -0
  51. package/dist/type/dialect.d.ts +7 -2
  52. package/dist/type/dialect.js +1 -0
  53. package/dist/type/entity.d.ts +1 -1
  54. package/dist/type/migration.d.ts +14 -1
  55. package/dist/util/string.util.js +6 -1
  56. package/package.json +5 -5
@@ -5,261 +5,252 @@
5
5
  * actual database schema.
6
6
  */
7
7
  import { canonicalToSql } from '../../schema/canonicalType.js';
8
- import { SchemaASTDiffer } from '../../schema/schemaASTDiffer.js';
8
+ import { diffSchemas } from '../../schema/schemaASTDiffer.js';
9
+ function resolveOptions(options) {
10
+ return {
11
+ checkTypes: options.checkTypes ?? true,
12
+ checkNullable: options.checkNullable ?? true,
13
+ checkIndexes: options.checkIndexes ?? true,
14
+ indexFacets: options.indexFacets ?? new Set(),
15
+ checkForeignKeys: options.checkForeignKeys ?? true,
16
+ checkDefaults: options.checkDefaults ?? false,
17
+ excludeTables: options.excludeTables ?? [],
18
+ dialect: options.dialect,
19
+ };
20
+ }
21
+ /**
22
+ * Compare an expected schema (from entities) with an actual one (from the database) and report every
23
+ * way they have drifted apart.
24
+ */
25
+ export function detectDrift(expectedAST, actualAST, options = {}) {
26
+ const opts = resolveOptions(options);
27
+ const diff = diffSchemas(expectedAST, actualAST, {
28
+ compareIndexes: opts.checkIndexes,
29
+ indexFacets: opts.indexFacets,
30
+ compareRelationships: opts.checkForeignKeys,
31
+ excludeTables: opts.excludeTables,
32
+ });
33
+ const drifts = [
34
+ ...detectTableDrifts(diff),
35
+ ...detectColumnDrifts(diff, opts),
36
+ ...detectIndexDrifts(diff),
37
+ ...detectRelationshipDrifts(diff),
38
+ ];
39
+ return {
40
+ status: calculateStatus(drifts),
41
+ drifts,
42
+ summary: createSummary(drifts),
43
+ generatedAt: new Date(),
44
+ };
45
+ }
9
46
  /**
10
- * Detects drift between expected and actual database schemas.
47
+ * Detect table-level drifts (missing/unexpected tables).
11
48
  */
12
- export class DriftDetector {
13
- expectedAST;
14
- actualAST;
15
- options;
16
- constructor(expectedAST, actualAST, options = {}) {
17
- this.expectedAST = expectedAST;
18
- this.actualAST = actualAST;
19
- this.options = {
20
- checkTypes: options.checkTypes ?? true,
21
- checkNullable: options.checkNullable ?? true,
22
- checkIndexes: options.checkIndexes ?? true,
23
- checkForeignKeys: options.checkForeignKeys ?? true,
24
- checkDefaults: options.checkDefaults ?? false,
25
- excludeTables: options.excludeTables ?? [],
26
- dialect: options.dialect,
27
- };
49
+ function detectTableDrifts(diff) {
50
+ const drifts = [];
51
+ for (const table of diff.tablesToCreate) {
52
+ drifts.push({
53
+ type: 'missing_table',
54
+ severity: 'critical',
55
+ table: table.name,
56
+ details: `Entity "${table.name}" exists but table not in database`,
57
+ suggestion: 'Run migrations to create table',
58
+ });
28
59
  }
29
- /**
30
- * Detect all schema drift.
31
- */
32
- detect() {
33
- const differ = new SchemaASTDiffer();
34
- const diff = differ.diff(this.expectedAST, this.actualAST, {
35
- compareIndexes: this.options.checkIndexes,
36
- compareRelationships: this.options.checkForeignKeys,
37
- excludeTables: this.options.excludeTables,
60
+ for (const table of diff.tablesToDrop) {
61
+ drifts.push({
62
+ type: 'unexpected_table',
63
+ severity: 'warning',
64
+ table: table.name,
65
+ details: `Table "${table.name}" exists in database but no matching entity`,
66
+ suggestion: 'Create entity or drop table',
38
67
  });
39
- const drifts = [
40
- ...this.detectTableDrifts(diff),
41
- ...this.detectColumnDrifts(diff),
42
- ...this.detectIndexDrifts(diff),
43
- ...this.detectRelationshipDrifts(diff),
44
- ];
45
- return {
46
- status: this.calculateStatus(drifts),
47
- drifts,
48
- summary: this.createSummary(drifts),
49
- generatedAt: new Date(),
50
- };
51
68
  }
52
- /**
53
- * Detect table-level drifts (missing/unexpected tables).
54
- */
55
- detectTableDrifts(diff) {
56
- const drifts = [];
57
- for (const table of diff.tablesToCreate) {
69
+ return drifts;
70
+ }
71
+ /**
72
+ * Detect column-level drifts.
73
+ */
74
+ function detectColumnDrifts(diff, opts) {
75
+ const drifts = [];
76
+ for (const colDiff of diff.columnDiffs) {
77
+ if (colDiff.type === 'add') {
58
78
  drifts.push({
59
- type: 'missing_table',
79
+ type: 'missing_column',
60
80
  severity: 'critical',
61
- table: table.name,
62
- details: `Entity "${table.name}" exists but table not in database`,
63
- suggestion: 'Run migrations to create table',
81
+ table: colDiff.table,
82
+ column: colDiff.column,
83
+ details: `Column "${colDiff.column}" expected but not found in database`,
84
+ suggestion: 'Run migration to add column',
64
85
  });
65
86
  }
66
- for (const table of diff.tablesToDrop) {
87
+ else if (colDiff.type === 'drop') {
67
88
  drifts.push({
68
- type: 'unexpected_table',
89
+ type: 'unexpected_column',
69
90
  severity: 'warning',
70
- table: table.name,
71
- details: `Table "${table.name}" exists in database but no matching entity`,
72
- suggestion: 'Create entity or drop table',
91
+ table: colDiff.table,
92
+ column: colDiff.column,
93
+ details: `Column "${colDiff.column}" exists in database but not in entity`,
94
+ suggestion: 'Add to entity or create migration to drop',
73
95
  });
74
96
  }
75
- return drifts;
76
- }
77
- /**
78
- * Detect column-level drifts.
79
- */
80
- detectColumnDrifts(diff) {
81
- const drifts = [];
82
- for (const colDiff of diff.columnDiffs) {
83
- if (colDiff.type === 'add') {
84
- drifts.push({
85
- type: 'missing_column',
86
- severity: 'critical',
87
- table: colDiff.table,
88
- column: colDiff.column,
89
- details: `Column "${colDiff.column}" expected but not found in database`,
90
- suggestion: 'Run migration to add column',
91
- });
92
- }
93
- else if (colDiff.type === 'drop') {
94
- drifts.push({
95
- type: 'unexpected_column',
96
- severity: 'warning',
97
- table: colDiff.table,
98
- column: colDiff.column,
99
- details: `Column "${colDiff.column}" exists in database but not in entity`,
100
- suggestion: 'Add to entity or create migration to drop',
101
- });
102
- }
103
- else if (colDiff.type === 'alter') {
104
- this.addAlterColumnDrifts(colDiff, drifts);
105
- }
97
+ else if (colDiff.type === 'alter') {
98
+ addAlterColumnDrifts(colDiff, drifts, opts);
106
99
  }
107
- return drifts;
108
100
  }
109
- /**
110
- * Add drifts for column alterations (type/nullable mismatches).
111
- */
112
- addAlterColumnDrifts(colDiff, drifts) {
113
- // Every check below compares the two sides, so there is nothing to report without both.
114
- if (!colDiff.expected || !colDiff.actual) {
115
- return;
116
- }
117
- if (this.options.checkTypes) {
118
- const expectedType = this.formatType(colDiff.expected.type);
119
- const actualType = this.formatType(colDiff.actual.type);
120
- if (expectedType !== actualType) {
121
- drifts.push({
122
- type: 'type_mismatch',
123
- severity: colDiff.isBreaking ? 'critical' : 'warning',
124
- table: colDiff.table,
125
- column: colDiff.column,
126
- expected: expectedType,
127
- actual: actualType,
128
- details: `Type mismatch for "${colDiff.column}": expected ${expectedType}, got ${actualType}`,
129
- suggestion: colDiff.isBreaking
130
- ? 'Data truncation risk! Create migration to fix.'
131
- : 'Create migration to align types',
132
- });
133
- }
101
+ return drifts;
102
+ }
103
+ /**
104
+ * Add drifts for column alterations (type/nullable mismatches).
105
+ */
106
+ function addAlterColumnDrifts(colDiff, drifts, opts) {
107
+ // Every check below compares the two sides, so there is nothing to report without both.
108
+ if (!colDiff.expected || !colDiff.actual) {
109
+ return;
110
+ }
111
+ if (opts.checkTypes) {
112
+ const expectedType = formatType(colDiff.expected.type, opts.dialect);
113
+ const actualType = formatType(colDiff.actual.type, opts.dialect);
114
+ if (expectedType !== actualType) {
115
+ drifts.push({
116
+ type: 'type_mismatch',
117
+ severity: colDiff.isBreaking ? 'critical' : 'warning',
118
+ table: colDiff.table,
119
+ column: colDiff.column,
120
+ expected: expectedType,
121
+ actual: actualType,
122
+ details: `Type mismatch for "${colDiff.column}": expected ${expectedType}, got ${actualType}`,
123
+ suggestion: colDiff.isBreaking
124
+ ? 'Data truncation risk! Create migration to fix.'
125
+ : 'Create migration to align types',
126
+ });
134
127
  }
135
- if (this.options.checkNullable && colDiff.expected.nullable !== colDiff.actual.nullable) {
128
+ }
129
+ if (opts.checkNullable && colDiff.expected.nullable !== colDiff.actual.nullable) {
130
+ drifts.push({
131
+ type: 'constraint_mismatch',
132
+ severity: 'warning',
133
+ table: colDiff.table,
134
+ column: colDiff.column,
135
+ expected: colDiff.expected.nullable ? 'NULLABLE' : 'NOT NULL',
136
+ actual: colDiff.actual.nullable ? 'NULLABLE' : 'NOT NULL',
137
+ details: `Nullable mismatch for "${colDiff.column}"`,
138
+ suggestion: 'Align nullable setting in entity or database',
139
+ });
140
+ }
141
+ if (opts.checkDefaults) {
142
+ const expected = String(colDiff.expected.defaultValue ?? 'NULL');
143
+ const actual = String(colDiff.actual.defaultValue ?? 'NULL');
144
+ if (expected !== actual) {
136
145
  drifts.push({
137
146
  type: 'constraint_mismatch',
138
- severity: 'warning',
147
+ severity: 'info',
139
148
  table: colDiff.table,
140
149
  column: colDiff.column,
141
- expected: colDiff.expected.nullable ? 'NULLABLE' : 'NOT NULL',
142
- actual: colDiff.actual.nullable ? 'NULLABLE' : 'NOT NULL',
143
- details: `Nullable mismatch for "${colDiff.column}"`,
144
- suggestion: 'Align nullable setting in entity or database',
150
+ expected,
151
+ actual,
152
+ details: `Default mismatch for "${colDiff.column}"`,
153
+ suggestion: 'Align the default in the entity or the database',
145
154
  });
146
155
  }
147
- if (this.options.checkDefaults) {
148
- const expected = String(colDiff.expected.defaultValue ?? 'NULL');
149
- const actual = String(colDiff.actual.defaultValue ?? 'NULL');
150
- if (expected !== actual) {
151
- drifts.push({
152
- type: 'constraint_mismatch',
153
- severity: 'info',
154
- table: colDiff.table,
155
- column: colDiff.column,
156
- expected,
157
- actual,
158
- details: `Default mismatch for "${colDiff.column}"`,
159
- suggestion: 'Align the default in the entity or the database',
160
- });
161
- }
162
- }
163
156
  }
164
- /**
165
- * Detect index drifts.
166
- */
167
- detectIndexDrifts(diff) {
168
- const drifts = [];
169
- for (const idxDiff of diff.indexDiffs) {
170
- if (idxDiff.type === 'create') {
171
- drifts.push({
172
- type: 'missing_index',
173
- severity: 'warning',
174
- table: idxDiff.table,
175
- index: idxDiff.name,
176
- details: `Index "${idxDiff.name}" expected but not found in database`,
177
- suggestion: 'Create index via migration',
178
- });
179
- }
180
- else if (idxDiff.type === 'drop') {
181
- drifts.push({
182
- type: 'unexpected_index',
183
- severity: 'info',
184
- table: idxDiff.table,
185
- index: idxDiff.name,
186
- details: `Index "${idxDiff.name}" exists in database but not defined in entity`,
187
- suggestion: 'Add @Field({ index }) or create migration to drop',
188
- });
189
- }
157
+ }
158
+ /**
159
+ * Detect index drifts.
160
+ */
161
+ function detectIndexDrifts(diff) {
162
+ const drifts = [];
163
+ for (const idxDiff of diff.indexDiffs) {
164
+ if (idxDiff.type === 'create') {
165
+ drifts.push({
166
+ type: 'missing_index',
167
+ severity: 'warning',
168
+ table: idxDiff.table,
169
+ index: idxDiff.name,
170
+ details: `Index "${idxDiff.name}" expected but not found in database`,
171
+ suggestion: 'Create index via migration',
172
+ });
190
173
  }
191
- return drifts;
192
- }
193
- /**
194
- * Detect relationship/FK drifts.
195
- */
196
- detectRelationshipDrifts(diff) {
197
- const drifts = [];
198
- for (const relDiff of diff.relationshipDiffs) {
199
- if (relDiff.type === 'create') {
200
- drifts.push({
201
- type: 'missing_relationship',
202
- severity: 'warning',
203
- table: relDiff.fromTable,
204
- relationship: relDiff.name,
205
- details: `FK "${relDiff.name}" expected but not found in database`,
206
- suggestion: 'Add FK constraint or remove relation from entity',
207
- });
208
- }
209
- else if (relDiff.type === 'drop') {
210
- drifts.push({
211
- type: 'unexpected_relationship',
212
- severity: 'info',
213
- table: relDiff.fromTable,
214
- relationship: relDiff.name,
215
- details: `FK "${relDiff.name}" exists in database but not in entity`,
216
- suggestion: 'Add relation to entity or drop FK',
217
- });
218
- }
174
+ else if (idxDiff.type === 'drop') {
175
+ drifts.push({
176
+ type: 'unexpected_index',
177
+ severity: 'info',
178
+ table: idxDiff.table,
179
+ index: idxDiff.name,
180
+ details: `Index "${idxDiff.name}" exists in database but not defined in entity`,
181
+ suggestion: 'Add @Field({ index }) or create migration to drop',
182
+ });
183
+ }
184
+ else if (idxDiff.type === 'alter') {
185
+ // No `expected`/`actual` here: the CLI prints those by interpolation, where an `IndexNode`
186
+ // renders as `[object Object]`. What differs is already spelled out in `description`.
187
+ drifts.push({
188
+ type: 'index_mismatch',
189
+ severity: 'warning',
190
+ table: idxDiff.table,
191
+ index: idxDiff.name,
192
+ details: `Index "${idxDiff.name}" differs from the entity (${idxDiff.description})`,
193
+ suggestion: 'Drop and recreate the index via migration',
194
+ });
219
195
  }
220
- return drifts;
221
- }
222
- /**
223
- * Calculate overall status based on drifts.
224
- */
225
- calculateStatus(drifts) {
226
- if (drifts.length === 0)
227
- return 'in_sync';
228
- const hasCritical = drifts.some((d) => d.severity === 'critical');
229
- if (hasCritical)
230
- return 'critical';
231
- return 'drifted';
232
- }
233
- /**
234
- * Create a summary of drifts by severity.
235
- */
236
- createSummary(drifts) {
237
- return {
238
- critical: drifts.filter((d) => d.severity === 'critical').length,
239
- warning: drifts.filter((d) => d.severity === 'warning').length,
240
- info: drifts.filter((d) => d.severity === 'info').length,
241
- };
242
196
  }
243
- /**
244
- * Format type for display.
245
- */
246
- formatType(type) {
247
- const dialect = this.options.dialect;
248
- if (!type || !dialect)
249
- return 'unknown';
250
- return canonicalToSql(type, dialect);
197
+ return drifts;
198
+ }
199
+ /**
200
+ * Detect relationship/FK drifts.
201
+ */
202
+ function detectRelationshipDrifts(diff) {
203
+ const drifts = [];
204
+ for (const relDiff of diff.relationshipDiffs) {
205
+ if (relDiff.type === 'create') {
206
+ drifts.push({
207
+ type: 'missing_relationship',
208
+ severity: 'warning',
209
+ table: relDiff.fromTable,
210
+ relationship: relDiff.name,
211
+ details: `FK "${relDiff.name}" expected but not found in database`,
212
+ suggestion: 'Add FK constraint or remove relation from entity',
213
+ });
214
+ }
215
+ else if (relDiff.type === 'drop') {
216
+ drifts.push({
217
+ type: 'unexpected_relationship',
218
+ severity: 'info',
219
+ table: relDiff.fromTable,
220
+ relationship: relDiff.name,
221
+ details: `FK "${relDiff.name}" exists in database but not in entity`,
222
+ suggestion: 'Add relation to entity or drop FK',
223
+ });
224
+ }
251
225
  }
226
+ return drifts;
227
+ }
228
+ /**
229
+ * Calculate overall status based on drifts.
230
+ */
231
+ function calculateStatus(drifts) {
232
+ if (drifts.length === 0)
233
+ return 'in_sync';
234
+ const hasCritical = drifts.some((d) => d.severity === 'critical');
235
+ if (hasCritical)
236
+ return 'critical';
237
+ return 'drifted';
252
238
  }
253
239
  /**
254
- * Create a DriftDetector for comparing expected vs actual schemas.
240
+ * Create a summary of drifts by severity.
255
241
  */
256
- export function createDriftDetector(expectedAST, actualAST, options) {
257
- return new DriftDetector(expectedAST, actualAST, options);
242
+ function createSummary(drifts) {
243
+ return {
244
+ critical: drifts.filter((d) => d.severity === 'critical').length,
245
+ warning: drifts.filter((d) => d.severity === 'warning').length,
246
+ info: drifts.filter((d) => d.severity === 'info').length,
247
+ };
258
248
  }
259
249
  /**
260
- * Quick check for schema drift.
250
+ * Format type for display.
261
251
  */
262
- export function detectDrift(expectedAST, actualAST, options) {
263
- const detector = new DriftDetector(expectedAST, actualAST, options);
264
- return detector.detect();
252
+ function formatType(type, dialect) {
253
+ if (!type || !dialect)
254
+ return 'unknown';
255
+ return canonicalToSql(type, dialect);
265
256
  }
@@ -3,4 +3,4 @@
3
3
  *
4
4
  * Detects schema drift between expected and actual database schemas.
5
5
  */
6
- export { createDriftDetector, DriftDetector, type DriftDetectorOptions, detectDrift, } from './driftDetector.js';
6
+ export { type DriftDetectorOptions, detectDrift, } from './driftDetector.js';
@@ -3,4 +3,4 @@
3
3
  *
4
4
  * Detects schema drift between expected and actual database schemas.
5
5
  */
6
- export { createDriftDetector, DriftDetector, detectDrift, } from './driftDetector.js';
6
+ export { detectDrift, } from './driftDetector.js';
@@ -0,0 +1,9 @@
1
+ import type { ColumnNode, TableNode } from '../../schema/types.js';
2
+ import type { FullColumnDefinition, TableDefinition } from '../builder/types.js';
3
+ /**
4
+ * A migration builder's table definition as the AST nodes the generators render from, so a hand-written
5
+ * `createTable` and an entity reach `generateCreateTableFromNode` in the same shape. Free functions and
6
+ * not generator methods: nothing here consults the dialect.
7
+ */
8
+ export declare function tableDefinitionToNode(def: TableDefinition): TableNode;
9
+ export declare function fullColumnDefinitionToNode(col: FullColumnDefinition, tableName: string): ColumnNode;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * A migration builder's table definition as the AST nodes the generators render from, so a hand-written
3
+ * `createTable` and an entity reach `generateCreateTableFromNode` in the same shape. Free functions and
4
+ * not generator methods: nothing here consults the dialect.
5
+ */
6
+ export function tableDefinitionToNode(def) {
7
+ const columns = new Map();
8
+ const pkNodes = [];
9
+ const table = {
10
+ name: def.name,
11
+ columns,
12
+ primaryKey: [], // placeholder
13
+ indexes: [],
14
+ schema: { tables: new Map(), relationships: [], indexes: [] },
15
+ incomingRelations: [],
16
+ outgoingRelations: [],
17
+ comment: def.comment,
18
+ };
19
+ for (const colDef of def.columns) {
20
+ const node = fullColumnDefinitionToNode(colDef, def.name);
21
+ node.table = table;
22
+ columns.set(node.name, node);
23
+ if (node.isPrimaryKey) {
24
+ pkNodes.push(node);
25
+ }
26
+ }
27
+ const finalPrimaryKey = def.primaryKey
28
+ ? def.primaryKey.map((name) => columns.get(name)).filter((c) => c !== undefined)
29
+ : pkNodes;
30
+ table.primaryKey = finalPrimaryKey;
31
+ for (const idxDef of def.indexes) {
32
+ table.indexes.push({ ...idxDef, table });
33
+ }
34
+ for (const fkDef of def.foreignKeys) {
35
+ const relNode = {
36
+ name: fkDef.name ?? `fk_${def.name}_${fkDef.columns.join('_')}`,
37
+ type: 'ManyToOne', // Builder default
38
+ from: {
39
+ table,
40
+ columns: fkDef.columns.map((name) => columns.get(name)).filter((c) => c !== undefined),
41
+ },
42
+ to: {
43
+ table: { name: fkDef.referencesTable },
44
+ columns: fkDef.referencesColumns.map((name) => ({ name })),
45
+ },
46
+ onDelete: fkDef.onDelete,
47
+ onUpdate: fkDef.onUpdate,
48
+ };
49
+ table.outgoingRelations.push(relNode);
50
+ }
51
+ return table;
52
+ }
53
+ export function fullColumnDefinitionToNode(col, tableName) {
54
+ return {
55
+ name: col.name,
56
+ type: col.type,
57
+ nullable: col.nullable,
58
+ defaultValue: col.defaultValue,
59
+ isPrimaryKey: col.primaryKey,
60
+ isAutoIncrement: col.autoIncrement,
61
+ isUnique: col.unique,
62
+ comment: col.comment,
63
+ table: { name: tableName },
64
+ referencedBy: [],
65
+ references: col.foreignKey
66
+ ? {
67
+ name: `fk_${tableName}_${col.name}`,
68
+ type: 'ManyToOne',
69
+ from: { table: { name: tableName }, columns: [] },
70
+ to: {
71
+ table: { name: col.foreignKey.table },
72
+ columns: col.foreignKey.columns.map((name) => ({ name })),
73
+ },
74
+ onDelete: col.foreignKey.onDelete,
75
+ onUpdate: col.foreignKey.onUpdate,
76
+ }
77
+ : undefined,
78
+ };
79
+ }
@@ -1,4 +1,5 @@
1
1
  import { isVectorCategory } from '../../schema/canonicalType.js';
2
+ import { indexColumns } from '../../schema/indexColumns.js';
2
3
  /**
3
4
  * An AST index as the generators and dialects want it. Spread rather than copied field by field:
4
5
  * rebuilding it by hand is how the partial-index `where` once vanished without a trace. The node-only
@@ -8,7 +9,8 @@ import { isVectorCategory } from '../../schema/canonicalType.js';
8
9
  export function indexNodeToSchema(index) {
9
10
  return {
10
11
  ...index,
11
- columns: index.entries ?? index.columns.map((col) => ({ column: col.name })),
12
- vectorType: index.columns.map((col) => col.type?.category).find(isVectorCategory),
12
+ vectorType: indexColumns(index)
13
+ .map((col) => col.type?.category)
14
+ .find(isVectorCategory),
13
15
  };
14
16
  }
@@ -42,7 +42,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
42
42
  const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
43
43
  indexes.push({
44
44
  name: indexName,
45
- columns: [{ column: columnName }],
45
+ entries: [{ column: columnName }],
46
46
  unique: !!field.unique,
47
47
  });
48
48
  }
@@ -85,7 +85,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
85
85
  */
86
86
  generateCreateIndex(tableName, index) {
87
87
  const key = {};
88
- for (const entry of index.columns) {
88
+ for (const entry of index.entries) {
89
89
  if (entry.expression || entry.length !== undefined || entry.nulls || entry.opsClass) {
90
90
  throw new TypeError(`mongodb does not support that index column option (index "${index.name}")`);
91
91
  }
@@ -146,7 +146,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
146
146
  if (!existingIndexes.has(indexName)) {
147
147
  indexesToAdd.push({
148
148
  name: indexName,
149
- columns: [{ column: columnName }],
149
+ entries: [{ column: columnName }],
150
150
  unique: !!field.unique,
151
151
  });
152
152
  }
@@ -1,4 +1,5 @@
1
1
  import type { AbstractSqlDialect } from '../../dialect/index.js';
2
+ import type { IndexFacet } from '../../schema/indexDifferences.js';
2
3
  import { SchemaAST } from '../../schema/schemaAST.js';
3
4
  import type { TableSchema } from '../../type/migration.js';
4
5
  /**
@@ -6,6 +7,8 @@ import type { TableSchema } from '../../type/migration.js';
6
7
  */
7
8
  export declare abstract class BaseSqlIntrospector {
8
9
  protected readonly dialect: AbstractSqlDialect;
10
+ /** Columns and uniqueness only; each introspector opts in to what its catalogue queries report. */
11
+ readonly indexFacets: ReadonlySet<IndexFacet>;
9
12
  constructor(dialect: AbstractSqlDialect);
10
13
  protected escapeId(identifier: string): string;
11
14
  /**