uql-orm 0.24.6 → 0.24.7

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/cockroachdb/crdbQuerierPool.d.ts +1 -1
  3. package/dist/cockroachdb/crdbQuerierPool.js +5 -4
  4. package/dist/dialect/abstractSqlDialect.d.ts +78 -27
  5. package/dist/dialect/abstractSqlDialect.js +189 -129
  6. package/dist/dialect/hydrateColumn.d.ts +16 -0
  7. package/dist/dialect/hydrateColumn.js +66 -0
  8. package/dist/dialect/jsonSql.d.ts +24 -0
  9. package/dist/dialect/jsonSql.js +39 -0
  10. package/dist/dialect/mysqlLikeSqlDialect.d.ts +5 -0
  11. package/dist/dialect/mysqlLikeSqlDialect.js +10 -1
  12. package/dist/dialect/pgLikeSqlDialect.d.ts +3 -7
  13. package/dist/dialect/pgLikeSqlDialect.js +2 -14
  14. package/dist/dialect/vectorCast.d.ts +15 -0
  15. package/dist/dialect/vectorCast.js +58 -0
  16. package/dist/entity/metadata/definition.d.ts +0 -1
  17. package/dist/entity/metadata/definition.js +1 -1
  18. package/dist/maria/mariaDialect.d.ts +3 -2
  19. package/dist/maria/mariaDialect.js +3 -18
  20. package/dist/maria/mariadbQuerierPool.js +6 -1
  21. package/dist/migrate/builder/migrationBuilder.d.ts +12 -16
  22. package/dist/migrate/builder/migrationBuilder.js +24 -59
  23. package/dist/migrate/builder/tableBuilder.js +0 -12
  24. package/dist/migrate/cli.d.ts +0 -1
  25. package/dist/migrate/cli.js +1 -1
  26. package/dist/migrate/codegen/entityCodeGenerator.js +0 -3
  27. package/dist/migrate/drift/driftDetector.js +17 -15
  28. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +8 -2
  29. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +10 -9
  30. package/dist/migrate/introspection/mysqlIntrospector.d.ts +0 -3
  31. package/dist/migrate/introspection/mysqlIntrospector.js +0 -9
  32. package/dist/migrate/introspection/postgresIntrospector.d.ts +0 -3
  33. package/dist/migrate/introspection/postgresIntrospector.js +0 -12
  34. package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
  35. package/dist/migrate/introspection/sqliteIntrospector.js +1 -9
  36. package/dist/migrate/migrator.d.ts +8 -0
  37. package/dist/migrate/migrator.js +19 -29
  38. package/dist/migrate/schemaGenerator.js +0 -12
  39. package/dist/neon/neonQuerierPool.d.ts +1 -1
  40. package/dist/neon/neonQuerierPool.js +6 -4
  41. package/dist/postgres/abstractPgQuerierPool.d.ts +6 -0
  42. package/dist/postgres/abstractPgQuerierPool.js +3 -0
  43. package/dist/postgres/pgNumericTypes.d.ts +41 -0
  44. package/dist/postgres/pgNumericTypes.js +35 -0
  45. package/dist/postgres/pgQuerierPool.d.ts +1 -1
  46. package/dist/postgres/pgQuerierPool.js +5 -4
  47. package/dist/querier/abstractSqlQuerier.d.ts +9 -2
  48. package/dist/querier/abstractSqlQuerier.js +31 -24
  49. package/dist/schema/canonicalType.js +2 -12
  50. package/dist/schema/schemaAST.js +0 -24
  51. package/dist/schema/schemaASTBuilder.js +0 -3
  52. package/dist/type/queryAggregate.d.ts +3 -0
  53. package/dist/util/field.util.d.ts +4 -0
  54. package/dist/util/field.util.js +12 -0
  55. package/dist/util/sqlLiteral.js +18 -15
  56. package/package.json +4 -4
@@ -1,4 +1,3 @@
1
- import { getMeta } from '../entity/index.js';
2
1
  import { QueryRaw, } from '../type/index.js';
3
2
  import { escapeSingleQuotes } from '../util/sqlLiteral.js';
4
3
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
@@ -41,6 +40,8 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
41
40
  commitTransactionCommand = 'COMMIT';
42
41
  rollbackTransactionCommand = 'ROLLBACK';
43
42
  alterColumnStrategy = 'separate-clauses';
43
+ /** `$N` placeholders carry their own index, so the upsert's assignments need no scratch context. */
44
+ upsertUpdateBindsInPlace = true;
44
45
  insertIdSource = 'returning';
45
46
  maxBindValues = 65535;
46
47
  /**
@@ -115,19 +116,6 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
115
116
  placeholder(index) {
116
117
  return `$${index}`;
117
118
  }
118
- /**
119
- * The same statement as the base, binding the assignments into the main context instead of a second
120
- * one: `$N` placeholders carry their own index, so the values need not be in statement order, and
121
- * computing them first is what keeps `appendInsertValues`' `onInsert` fields out of the update set.
122
- */
123
- upsert(ctx, entity, conflictPaths, payload, extraReturning = '') {
124
- const meta = getMeta(entity);
125
- const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, this.upsertExcluded);
126
- const keys = this.getUpsertConflictPathsStr(meta, conflictPaths);
127
- const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
128
- this.appendInsertValues(ctx, entity, payload);
129
- ctx.append(` ON CONFLICT (${keys}) ${onConflict} ${this.returningId(entity)}${extraReturning}`);
130
- }
131
119
  /**
132
120
  * `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
133
121
  * (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `to_tsquery`, which
@@ -30,3 +30,18 @@ export declare function resolveVectorCast(field: {
30
30
  * declaring `type: 'sparsevec'` still hands UQL the dense array its field type promises.
31
31
  */
32
32
  export declare function toSparsevecLiteral(values: readonly unknown[]): string;
33
+ /**
34
+ * The inverse of the two literals above. pgvector hands a vector column back as **text**, so a read
35
+ * that did not parse it returned a string from a field whose declared type is `number[]`: invisible
36
+ * to the compiler, and invisible to any mocked test, because a mock returns the array the entity
37
+ * promises. It surfaces only as arithmetic quietly producing nonsense on real rows.
38
+ *
39
+ * Driven by `cast`, never by the shape of the text, so this is the exact mirror of the write side:
40
+ * a `sparsevec` column is read as `{1:1,3:2}/3` because that is what it was written as, and a dense
41
+ * one as `[1,2,3]`. Both return the dense array the field type promises, whichever width the column
42
+ * has. Sniffing the string instead would guess at a type the caller already knows.
43
+ *
44
+ * Returns `undefined` when the text does not match the column's own format, so a caller can keep the
45
+ * raw value rather than replace it with something invented.
46
+ */
47
+ export declare function parseVectorLiteral(raw: string, cast: VectorCast): number[] | undefined;
@@ -39,3 +39,61 @@ export function toSparsevecLiteral(values) {
39
39
  .join(',');
40
40
  return `{${pairs}}/${values.length}`;
41
41
  }
42
+ /**
43
+ * The inverse of the two literals above. pgvector hands a vector column back as **text**, so a read
44
+ * that did not parse it returned a string from a field whose declared type is `number[]`: invisible
45
+ * to the compiler, and invisible to any mocked test, because a mock returns the array the entity
46
+ * promises. It surfaces only as arithmetic quietly producing nonsense on real rows.
47
+ *
48
+ * Driven by `cast`, never by the shape of the text, so this is the exact mirror of the write side:
49
+ * a `sparsevec` column is read as `{1:1,3:2}/3` because that is what it was written as, and a dense
50
+ * one as `[1,2,3]`. Both return the dense array the field type promises, whichever width the column
51
+ * has. Sniffing the string instead would guess at a type the caller already knows.
52
+ *
53
+ * Returns `undefined` when the text does not match the column's own format, so a caller can keep the
54
+ * raw value rather than replace it with something invented.
55
+ */
56
+ export function parseVectorLiteral(raw, cast) {
57
+ const text = raw.trim();
58
+ return cast === 'sparsevec' ? parseSparse(text) : parseDense(text);
59
+ }
60
+ const SPARSE_LITERAL = /^\{(.*)\}\/(\d+)$/;
61
+ /** `{1:1,3:2}/3` expanded to the dense array the field type promises, zeros included. */
62
+ function parseSparse(text) {
63
+ const sparse = SPARSE_LITERAL.exec(text);
64
+ if (!sparse)
65
+ return undefined;
66
+ const dense = new Array(Number(sparse[2])).fill(0);
67
+ if (!sparse[1])
68
+ return dense;
69
+ for (const pair of sparse[1].split(',')) {
70
+ // Split into exactly two non-empty parts before converting: `Number('')` is 0, not NaN, so a
71
+ // truncated `{1:}/3` would otherwise decode to a confident zero instead of being refused.
72
+ const parts = pair.split(':');
73
+ if (parts.length !== 2 || !parts[0] || !parts[1])
74
+ return undefined;
75
+ const [index, value] = parts.map(Number);
76
+ if (!Number.isInteger(index) || index < 1 || index > dense.length || Number.isNaN(value))
77
+ return undefined;
78
+ dense[index - 1] = value;
79
+ }
80
+ return dense;
81
+ }
82
+ /**
83
+ * `[1,0,2]`, whatever width the column has.
84
+ *
85
+ * A dense literal is valid JSON by construction, so parsing it as JSON is both stricter and cheaper
86
+ * than splitting: `[1,,2]` throws here, where `split(',').map(Number)` would have turned the hole
87
+ * into a 0.
88
+ */
89
+ function parseDense(text) {
90
+ if (!text.startsWith('[') || !text.endsWith(']'))
91
+ return undefined;
92
+ try {
93
+ const dense = JSON.parse(text);
94
+ return Array.isArray(dense) && dense.every((n) => typeof n === 'number') ? dense : undefined;
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ }
@@ -26,6 +26,5 @@ type MemberSpecs = {
26
26
  export declare function applyMembers<E>(entity: Type<E>, specs: MemberSpecs | undefined): void;
27
27
  export declare function defineEntity<E>(entity: Type<E>, opts?: EntityOptions<E>): EntityMeta<E>;
28
28
  export declare function getEntities(): Type<unknown>[];
29
- export declare function ensureMeta<E>(entity: Type<E>): EntityMeta<E>;
30
29
  export declare function getMeta<E>(entity: Type<E>): EntityMeta<E>;
31
30
  export {};
@@ -163,7 +163,7 @@ export function getEntities() {
163
163
  return acc;
164
164
  }, []);
165
165
  }
166
- export function ensureMeta(entity) {
166
+ function ensureMeta(entity) {
167
167
  let meta = metas.get(entity);
168
168
  if (meta) {
169
169
  return meta;
@@ -1,5 +1,5 @@
1
1
  import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
2
- import type { DialectFeatures, FieldOptions, IndexFeature, IndexSchema, QueryConflictPaths, QueryContext, Type, VectorDistance } from '../type/index.js';
2
+ import type { DialectFeatures, FieldOptions, IndexFeature, IndexSchema, QueryContext, Type, VectorDistance } from '../type/index.js';
3
3
  export declare class MariaDialect extends MysqlLikeSqlDialect {
4
4
  readonly dialectName = "mariadb";
5
5
  readonly insertIdSource = "returning";
@@ -11,7 +11,8 @@ export declare class MariaDialect extends MysqlLikeSqlDialect {
11
11
  protected readonly indexFeatures: Set<IndexFeature>;
12
12
  /** Unlike MySQL: `VECTOR(n)` takes its dimension, and its vector index is declared inline. */
13
13
  protected readonly featureOverrides: Partial<DialectFeatures>;
14
- upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
14
+ /** MariaDB 10.5+ supports `INSERT ... RETURNING`, so the ids are exact per row. */
15
+ protected upsertReturning<E>(entity: Type<E>): string;
15
16
  /**
16
17
  * MariaDB supports neither MySQL's `->`/`->>` shorthand nor the base's chained form. `JSON_VALUE`
17
18
  * reads a scalar and `JSON_EXTRACT` the subtree that the array operators need.
@@ -1,7 +1,6 @@
1
1
  import { jsonPath } from '../dialect/jsonSql.js';
2
2
  import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
3
3
  import { isVectorFieldType } from '../dialect/vectorCast.js';
4
- import { getMeta } from '../entity/index.js';
5
4
  export class MariaDialect extends MysqlLikeSqlDialect {
6
5
  dialectName = 'mariadb';
7
6
  // MariaDB 10.5+ supports `INSERT ... RETURNING` (see `insert` below), so IDs are exact per row.
@@ -17,23 +16,9 @@ export class MariaDialect extends MysqlLikeSqlDialect {
17
16
  vectorSupportsLength: true,
18
17
  inlineVectorIndex: true,
19
18
  };
20
- upsert(ctx, entity, conflictPaths, payload) {
21
- const meta = getMeta(entity);
22
- const updateCtx = this.createContext();
23
- const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `VALUES(${name})`);
24
- const returning = this.returningId(entity);
25
- if (update) {
26
- this.appendInsertValues(ctx, entity, payload);
27
- ctx.append(` ON DUPLICATE KEY UPDATE ${update} ${returning}`);
28
- ctx.pushValue(...updateCtx.values);
29
- }
30
- else {
31
- const insertCtx = this.createContext();
32
- this.appendInsertValues(insertCtx, entity, payload);
33
- ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
34
- ctx.append(' ' + returning);
35
- ctx.pushValue(...insertCtx.values);
36
- }
19
+ /** MariaDB 10.5+ supports `INSERT ... RETURNING`, so the ids are exact per row. */
20
+ upsertReturning(entity) {
21
+ return ` ${this.returningId(entity)}`;
37
22
  }
38
23
  /**
39
24
  * MariaDB supports neither MySQL's `->`/`->>` shorthand nor the base's chained form. `JSON_VALUE`
@@ -7,7 +7,12 @@ export class MariadbQuerierPool extends AbstractSqlQuerierPool {
7
7
  pool;
8
8
  constructor(opts, extra) {
9
9
  super(new MariaDialect({ namingStrategy: extra?.namingStrategy }), extra);
10
- this.pool = createPool(opts);
10
+ // `mariadb` defaults to handing BIGINT back as a BigInt, and uql maps `type: Number` to BIGINT
11
+ // (see `schema/canonicalType.ts`), so every auto-increment id reached a field declared `number`
12
+ // as `9n` without this. Same trade as the pg pools: exact to 2^53, and `...opts` wins for a
13
+ // caller who needs more. This belongs to the pool, not to the suites - it lived in
14
+ // `mariadbQuerier.test.ts`, which meant the tests passed on behaviour the library never shipped.
15
+ this.pool = createPool({ bigIntAsNumber: true, ...opts });
11
16
  // `mariadb`'s own `createPool` already attaches a silent no-op 'error'
12
17
  // listener (so a dropped connection can't crash the process), but its
13
18
  // `Pool` type only declares `on` for 'acquire' | 'connection' | 'enqueue'
@@ -9,6 +9,15 @@ import type { ForeignKeyAction } from '../../schema/types.js';
9
9
  import type { IndexColumnInput, IndexOptions } from '../../type/index.js';
10
10
  import type { SqlQuerier } from '../../type/querier.js';
11
11
  import type { AnyMigrationOperation, IAlterTableBuilder, IColumnBuilder, IColumnFactory, IMigrationBuilder, ITableBuilder } from './types.js';
12
+ type ForeignKeyTarget = {
13
+ table: string;
14
+ columns: string[];
15
+ };
16
+ type ForeignKeyOptions = {
17
+ name?: string;
18
+ onDelete?: ForeignKeyAction;
19
+ onUpdate?: ForeignKeyAction;
20
+ };
12
21
  /**
13
22
  * Records migration operations without executing them.
14
23
  * Use for migration code generation and dry-run scenarios.
@@ -28,14 +37,7 @@ export declare class OperationRecorder implements IMigrationBuilder {
28
37
  renameColumn(tableName: string, oldName: string, newName: string): Promise<void>;
29
38
  createIndex(tableName: string, columns: readonly IndexColumnInput[], options?: IndexOptions): Promise<void>;
30
39
  dropIndex(tableName: string, indexName: string): Promise<void>;
31
- addForeignKey(tableName: string, columns: string[], target: {
32
- table: string;
33
- columns: string[];
34
- }, options?: {
35
- name?: string;
36
- onDelete?: ForeignKeyAction;
37
- onUpdate?: ForeignKeyAction;
38
- }): Promise<void>;
40
+ addForeignKey(tableName: string, columns: string[], target: ForeignKeyTarget, options?: ForeignKeyOptions): Promise<void>;
39
41
  dropForeignKey(tableName: string, constraintName: string): Promise<void>;
40
42
  raw(sql: string): Promise<void>;
41
43
  getOperations(): AnyMigrationOperation[];
@@ -74,16 +76,10 @@ export declare class MigrationBuilder extends OperationRecorder {
74
76
  renameColumn(tableName: string, oldName: string, newName: string): Promise<void>;
75
77
  createIndex(tableName: string, columns: readonly IndexColumnInput[], options?: IndexOptions): Promise<void>;
76
78
  dropIndex(tableName: string, indexName: string): Promise<void>;
77
- addForeignKey(tableName: string, columns: string[], target: {
78
- table: string;
79
- columns: string[];
80
- }, options?: {
81
- name?: string;
82
- onDelete?: ForeignKeyAction;
83
- onUpdate?: ForeignKeyAction;
84
- }): Promise<void>;
79
+ addForeignKey(tableName: string, columns: string[], target: ForeignKeyTarget, options?: ForeignKeyOptions): Promise<void>;
85
80
  dropForeignKey(tableName: string, constraintName: string): Promise<void>;
86
81
  private getCreateTableStatements;
87
82
  private execute;
88
83
  private operationToSql;
89
84
  }
85
+ export {};
@@ -32,6 +32,27 @@ function createIndexOperation(tableName, columns, options = {}) {
32
32
  },
33
33
  };
34
34
  }
35
+ /**
36
+ * The one shape of an `addForeignKey` operation, `NO ACTION` defaults included.
37
+ *
38
+ * Three callers build it and differ only in what they do with the result: the table builder records
39
+ * it through its parent, the recorder records it directly, and the executing builder also runs it.
40
+ * Spelled out three times, a changed default would have had to be found in all three.
41
+ */
42
+ function addForeignKeyOperation(tableName, columns, target, options = {}) {
43
+ return {
44
+ type: 'addForeignKey',
45
+ tableName,
46
+ foreignKey: {
47
+ name: options.name,
48
+ columns,
49
+ referencesTable: target.table,
50
+ referencesColumns: target.columns,
51
+ onDelete: options.onDelete ?? 'NO ACTION',
52
+ onUpdate: options.onUpdate ?? 'NO ACTION',
53
+ },
54
+ };
55
+ }
35
56
  /**
36
57
  * Declare one column through the same vocabulary `createTable` uses. A throwaway {@link TableBuilder}
37
58
  * is that vocabulary: `addColumn`/`alterColumn` used to take a bare {@link IColumnBuilder}, which
@@ -95,18 +116,7 @@ class AlterTableBuilder {
95
116
  return this;
96
117
  }
97
118
  addForeignKey(columns, target, options) {
98
- this.parentBuilder.recordOperationSync({
99
- type: 'addForeignKey',
100
- tableName: this.tableName,
101
- foreignKey: {
102
- name: options?.name,
103
- columns,
104
- referencesTable: target.table,
105
- referencesColumns: target.columns,
106
- onDelete: options?.onDelete ?? 'NO ACTION',
107
- onUpdate: options?.onUpdate ?? 'NO ACTION',
108
- },
109
- });
119
+ this.parentBuilder.recordOperationSync(addForeignKeyOperation(this.tableName, columns, target, options));
110
120
  return this;
111
121
  }
112
122
  dropForeignKey(name) {
@@ -124,9 +134,6 @@ class AlterTableBuilder {
124
134
  */
125
135
  export class OperationRecorder {
126
136
  operations = [];
127
- // ============================================================================
128
- // Table Operations
129
- // ============================================================================
130
137
  async createTable(name, callback) {
131
138
  const builder = new TableBuilder(name);
132
139
  callback(builder);
@@ -154,9 +161,6 @@ export class OperationRecorder {
154
161
  const builder = new AlterTableBuilder(name, this);
155
162
  callback(builder);
156
163
  }
157
- // ============================================================================
158
- // Column Operations
159
- // ============================================================================
160
164
  async addColumn(tableName, callback) {
161
165
  this.recordOperationSync({
162
166
  type: 'addColumn',
@@ -188,9 +192,6 @@ export class OperationRecorder {
188
192
  newName,
189
193
  });
190
194
  }
191
- // ============================================================================
192
- // Index Operations
193
- // ============================================================================
194
195
  async createIndex(tableName, columns, options) {
195
196
  this.recordOperationSync(createIndexOperation(tableName, columns, options));
196
197
  }
@@ -201,22 +202,8 @@ export class OperationRecorder {
201
202
  indexName,
202
203
  });
203
204
  }
204
- // ============================================================================
205
- // Foreign Key Operations
206
- // ============================================================================
207
205
  async addForeignKey(tableName, columns, target, options = {}) {
208
- this.recordOperationSync({
209
- type: 'addForeignKey',
210
- tableName,
211
- foreignKey: {
212
- name: options.name,
213
- columns,
214
- referencesTable: target.table,
215
- referencesColumns: target.columns,
216
- onDelete: options.onDelete ?? 'NO ACTION',
217
- onUpdate: options.onUpdate ?? 'NO ACTION',
218
- },
219
- });
206
+ this.recordOperationSync(addForeignKeyOperation(tableName, columns, target, options));
220
207
  }
221
208
  async dropForeignKey(tableName, constraintName) {
222
209
  this.recordOperationSync({
@@ -225,18 +212,12 @@ export class OperationRecorder {
225
212
  constraintName,
226
213
  });
227
214
  }
228
- // ============================================================================
229
- // Raw SQL
230
- // ============================================================================
231
215
  async raw(sql) {
232
216
  this.recordOperationSync({
233
217
  type: 'raw',
234
218
  sql,
235
219
  });
236
220
  }
237
- // ============================================================================
238
- // Operation Access
239
- // ============================================================================
240
221
  getOperations() {
241
222
  return [...this.operations];
242
223
  }
@@ -284,9 +265,7 @@ export class MigrationBuilder extends OperationRecorder {
284
265
  this.operations.push(operation);
285
266
  await this.querier.run(sql);
286
267
  }
287
- // ============================================================================
288
268
  // Override async methods to execute immediately
289
- // ============================================================================
290
269
  async createTable(name, callback) {
291
270
  const builder = new TableBuilder(name);
292
271
  callback(builder);
@@ -370,18 +349,7 @@ export class MigrationBuilder extends OperationRecorder {
370
349
  await this.execute(operation);
371
350
  }
372
351
  async addForeignKey(tableName, columns, target, options = {}) {
373
- const operation = {
374
- type: 'addForeignKey',
375
- tableName,
376
- foreignKey: {
377
- name: options.name,
378
- columns,
379
- referencesTable: target.table,
380
- referencesColumns: target.columns,
381
- onDelete: options.onDelete ?? 'NO ACTION',
382
- onUpdate: options.onUpdate ?? 'NO ACTION',
383
- },
384
- };
352
+ const operation = addForeignKeyOperation(tableName, columns, target, options);
385
353
  this.operations.push(operation);
386
354
  await this.execute(operation);
387
355
  }
@@ -394,9 +362,6 @@ export class MigrationBuilder extends OperationRecorder {
394
362
  this.operations.push(operation);
395
363
  await this.execute(operation);
396
364
  }
397
- // ============================================================================
398
- // Private Methods
399
- // ============================================================================
400
365
  getCreateTableStatements(operation) {
401
366
  return this.sqlGenerator.generateCreateTableFromDefinition(operation.table);
402
367
  }
@@ -65,9 +65,6 @@ export class TableBuilder {
65
65
  constructor(name) {
66
66
  this._name = name;
67
67
  }
68
- // ============================================================================
69
- // Numeric Types
70
- // ============================================================================
71
68
  id(name = 'id', options = {}) {
72
69
  return this.add(name, { category: 'integer' }, { ...options, primaryKey: true, autoIncrement: true });
73
70
  }
@@ -138,9 +135,6 @@ export class TableBuilder {
138
135
  this._columnBuilders.push(column);
139
136
  return column;
140
137
  }
141
- // ============================================================================
142
- // Convenience Methods
143
- // ============================================================================
144
138
  createdAt() {
145
139
  return this.add('createdAt', { category: 'timestamp' }, { defaultValue: t.now() });
146
140
  }
@@ -151,9 +145,6 @@ export class TableBuilder {
151
145
  this.createdAt();
152
146
  this.updatedAt();
153
147
  }
154
- // ============================================================================
155
- // Indexes & Constraints
156
- // ============================================================================
157
148
  primaryKey(columns) {
158
149
  this._primaryKey = columns;
159
150
  return this;
@@ -186,9 +177,6 @@ export class TableBuilder {
186
177
  this._foreignKeyBuilders.push(fk);
187
178
  return fk;
188
179
  }
189
- // ============================================================================
190
- // Utilities
191
- // ============================================================================
192
180
  comment(text) {
193
181
  this._comment = text;
194
182
  return this;
@@ -17,4 +17,3 @@ export declare function runGenerateFromEntities(migrator: Migrator, args: string
17
17
  export declare function runSync(migrator: Migrator, args: string[], config: Partial<Config>): Promise<void>;
18
18
  export declare function runGenerateFromDb(migrator: Migrator, args: string[], config: Partial<Config>): Promise<void>;
19
19
  export declare function runDriftCheck(migrator: Migrator, config: Partial<Config>): Promise<void>;
20
- export declare function printHelp(): void;
@@ -309,7 +309,7 @@ function printDriftGroup(title, drifts, icon, showSuggestion) {
309
309
  console.log('');
310
310
  }
311
311
  }
312
- export function printHelp() {
312
+ function printHelp() {
313
313
  console.log(`
314
314
  uql-orm/migrate - Database migration tool for uql ORM
315
315
 
@@ -268,9 +268,6 @@ export class EntityCodeGenerator {
268
268
  }
269
269
  return lines.join('\n');
270
270
  }
271
- // ============================================================================
272
- // Helper Methods
273
- // ============================================================================
274
271
  /**
275
272
  * Get decorator name for relation type.
276
273
  */
@@ -110,7 +110,11 @@ export class DriftDetector {
110
110
  * Add drifts for column alterations (type/nullable mismatches).
111
111
  */
112
112
  addAlterColumnDrifts(colDiff, drifts) {
113
- if (this.options.checkTypes && colDiff.expected && colDiff.actual) {
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) {
114
118
  const expectedType = this.formatType(colDiff.expected.type);
115
119
  const actualType = this.formatType(colDiff.actual.type);
116
120
  if (expectedType !== actualType) {
@@ -128,21 +132,19 @@ export class DriftDetector {
128
132
  });
129
133
  }
130
134
  }
131
- if (this.options.checkNullable && colDiff.expected && colDiff.actual) {
132
- if (colDiff.expected.nullable !== colDiff.actual.nullable) {
133
- drifts.push({
134
- type: 'constraint_mismatch',
135
- severity: 'warning',
136
- table: colDiff.table,
137
- column: colDiff.column,
138
- expected: colDiff.expected.nullable ? 'NULLABLE' : 'NOT NULL',
139
- actual: colDiff.actual.nullable ? 'NULLABLE' : 'NOT NULL',
140
- details: `Nullable mismatch for "${colDiff.column}"`,
141
- suggestion: 'Align nullable setting in entity or database',
142
- });
143
- }
135
+ if (this.options.checkNullable && colDiff.expected.nullable !== colDiff.actual.nullable) {
136
+ drifts.push({
137
+ type: 'constraint_mismatch',
138
+ severity: 'warning',
139
+ table: colDiff.table,
140
+ 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',
145
+ });
144
146
  }
145
- if (this.options.checkDefaults && colDiff.expected && colDiff.actual) {
147
+ if (this.options.checkDefaults) {
146
148
  const expected = String(colDiff.expected.defaultValue ?? 'NULL');
147
149
  const actual = String(colDiff.actual.defaultValue ?? 'NULL');
148
150
  if (expected !== actual) {
@@ -77,8 +77,14 @@ export declare abstract class AbstractSqlSchemaIntrospector extends BaseSqlIntro
77
77
  protected abstract getForeignKeysQuery(tableName: string): string;
78
78
  /** SQL query to get primary key columns. Parameter: tableName (for PRAGMA-style). */
79
79
  protected abstract getPrimaryKeyQuery(tableName: string): string;
80
- /** Extract table name from a row returned by getTableNamesQuery. */
81
- protected abstract mapTableNameRow(row: RawRow): string;
80
+ /**
81
+ * Extract table name from a row returned by getTableNamesQuery.
82
+ *
83
+ * Defaults to `information_schema`'s own column, which is what every engine with an
84
+ * `information_schema` returns and what Postgres and MySQL both restated identically. SQLite reads
85
+ * `sqlite_master` instead and overrides.
86
+ */
87
+ protected mapTableNameRow(row: RawRow): string;
82
88
  /** Map column query results to ColumnSchema array. Allows async for SQLite's unique column check. */
83
89
  protected abstract mapColumnsResult(read: TableRowReader, tableName: string, results: RawRow[]): Promise<ColumnSchema[]>;
84
90
  /** Map index query results to IndexSchema array. Allows async for SQLite's index_info calls. */
@@ -25,9 +25,6 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
25
25
  super(pool.dialect);
26
26
  this.pool = pool;
27
27
  }
28
- // ============================================================================
29
- // Template Methods (shared control flow)
30
- // ============================================================================
31
28
  async getTableSchema(tableName) {
32
29
  return this.withSqlQuerier(async (querier) => {
33
30
  const read = createTableRowReader(querier);
@@ -91,9 +88,6 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
91
88
  const results = await read(this.getPrimaryKeyQuery(tableName), this.getPrimaryKeyParams(tableName));
92
89
  return this.mapPrimaryKeyResult(results);
93
90
  }
94
- // ============================================================================
95
- // Parameter Methods (can be overridden by dialects)
96
- // ============================================================================
97
91
  tableExistsParams(tableName) {
98
92
  return [tableName];
99
93
  }
@@ -109,9 +103,6 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
109
103
  getPrimaryKeyParams(tableName) {
110
104
  return [tableName];
111
105
  }
112
- // ============================================================================
113
- // Shared Utilities
114
- // ============================================================================
115
106
  /**
116
107
  * Normalize referential action string to standard type.
117
108
  */
@@ -138,6 +129,16 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
138
129
  }
139
130
  return Number(value);
140
131
  }
132
+ /**
133
+ * Extract table name from a row returned by getTableNamesQuery.
134
+ *
135
+ * Defaults to `information_schema`'s own column, which is what every engine with an
136
+ * `information_schema` returns and what Postgres and MySQL both restated identically. SQLite reads
137
+ * `sqlite_master` instead and overrides.
138
+ */
139
+ mapTableNameRow(row) {
140
+ return row['table_name'];
141
+ }
141
142
  /**
142
143
  * Map primary key query results to column names, in key order. `information_schema` gives every SQL
143
144
  * engine here a `column_name` per row; SQLite reads its key off `PRAGMA table_info` instead and
@@ -14,9 +14,6 @@ export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
14
14
  protected getIndexesQuery(_tableName: string): string;
15
15
  protected getForeignKeysQuery(_tableName: string): string;
16
16
  protected getPrimaryKeyQuery(_tableName: string): string;
17
- protected mapTableNameRow(row: {
18
- table_name: string;
19
- }): string;
20
17
  protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: MysqlColumnRow[]): Promise<ColumnSchema[]>;
21
18
  protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
22
19
  index_name: string;
@@ -4,9 +4,6 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
4
4
  * Works with both MySQL and MariaDB as they share the same information_schema structure.
5
5
  */
6
6
  export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
7
- // ============================================================================
8
- // SQL Queries (dialect-specific)
9
- // ============================================================================
10
7
  getTableNamesQuery() {
11
8
  return /*sql*/ `
12
9
  SELECT TABLE_NAME as table_name
@@ -95,12 +92,6 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
95
92
  ORDER BY ORDINAL_POSITION
96
93
  `;
97
94
  }
98
- // ============================================================================
99
- // Internal Types
100
- // ============================================================================
101
- mapTableNameRow(row) {
102
- return row.table_name;
103
- }
104
95
  async mapColumnsResult(_read, _tableName, results) {
105
96
  return results.map((row) => ({
106
97
  name: row.column_name,
@@ -11,9 +11,6 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
11
11
  protected getIndexesQuery(_tableName: string): string;
12
12
  protected getForeignKeysQuery(_tableName: string): string;
13
13
  protected getPrimaryKeyQuery(_tableName: string): string;
14
- protected mapTableNameRow(row: {
15
- table_name: string;
16
- }): string;
17
14
  protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: PostgresColumnRow[]): Promise<ColumnSchema[]>;
18
15
  protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
19
16
  index_name: string;