uql-orm 0.54.0 → 0.56.0

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 (162) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +4 -4
  4. package/dist/bunSql/bunSql.util.d.ts +3 -14
  5. package/dist/bunSql/bunSql.util.js +33 -56
  6. package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
  7. package/dist/bunSql/bunSqlQuerier.js +7 -13
  8. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
  9. package/dist/bunSql/bunSqlQuerierPool.js +25 -10
  10. package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
  11. package/dist/cockroachdb/crdbQuerierPool.js +0 -4
  12. package/dist/cockroachdb/index.d.ts +0 -1
  13. package/dist/cockroachdb/index.js +0 -1
  14. package/dist/d1/d1SqliteDialect.d.ts +5 -0
  15. package/dist/d1/d1SqliteDialect.js +7 -0
  16. package/dist/dialect/abstractSqlDialect.d.ts +9 -9
  17. package/dist/dialect/abstractSqlDialect.js +38 -51
  18. package/dist/dialect/hydrateColumn.js +2 -2
  19. package/dist/dialect/mergeSqlDialect.d.ts +2 -2
  20. package/dist/dialect/mergeSqlDialect.js +0 -4
  21. package/dist/dialect/mysqlLikeSqlDialect.js +2 -3
  22. package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
  23. package/dist/dialect/pgLikeSqlDialect.js +14 -4
  24. package/dist/dialect/queryJoins.js +2 -4
  25. package/dist/entity/index.d.ts +1 -1
  26. package/dist/entity/index.js +1 -1
  27. package/dist/entity/metadata/definition.d.ts +5 -1
  28. package/dist/entity/metadata/definition.js +32 -26
  29. package/dist/libsql/index.d.ts +0 -1
  30. package/dist/libsql/index.js +0 -1
  31. package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
  32. package/dist/libsql/libsqlQuerierPool.js +2 -5
  33. package/dist/maria/mariadbQuerier.d.ts +0 -3
  34. package/dist/maria/mariadbQuerier.js +6 -7
  35. package/dist/maria/mariadbQuerierPool.js +4 -7
  36. package/dist/migrate/builder/splitSqlStatements.js +2 -2
  37. package/dist/migrate/cli.js +2 -9
  38. package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
  39. package/dist/migrate/ddl/index.d.ts +3 -3
  40. package/dist/migrate/ddl/index.js +3 -3
  41. package/dist/migrate/ddl/pgIndexDdl.d.ts +5 -0
  42. package/dist/migrate/ddl/pgIndexDdl.js +9 -0
  43. package/dist/migrate/drift/driftDetector.js +21 -8
  44. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
  45. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
  46. package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
  47. package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
  48. package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
  49. package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
  50. package/dist/migrate/migrator.d.ts +5 -2
  51. package/dist/migrate/migrator.js +9 -14
  52. package/dist/migrate/schemaGenerator.js +2 -6
  53. package/dist/mongo/index.d.ts +0 -1
  54. package/dist/mongo/index.js +0 -1
  55. package/dist/mongo/mongoDialect.d.ts +1 -2
  56. package/dist/mongo/mongoDialect.js +7 -21
  57. package/dist/mongo/mongodbQuerier.js +12 -11
  58. package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
  59. package/dist/mongo/mongodbQuerierPool.js +2 -2
  60. package/dist/mssql/mssqlDialect.d.ts +3 -5
  61. package/dist/mssql/mssqlDialect.js +9 -8
  62. package/dist/mssql/mssqlQuerier.d.ts +13 -6
  63. package/dist/mssql/mssqlQuerier.js +30 -75
  64. package/dist/mssql/mssqlQuerierPool.js +2 -0
  65. package/dist/mssql/mssqlWireTypes.d.ts +3 -4
  66. package/dist/mssql/mssqlWireTypes.js +5 -8
  67. package/dist/mysql/index.d.ts +0 -1
  68. package/dist/mysql/index.js +0 -1
  69. package/dist/mysql/mysql2Querier.d.ts +1 -4
  70. package/dist/mysql/mysql2Querier.js +0 -3
  71. package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
  72. package/dist/mysql/mysql2QuerierPool.js +5 -3
  73. package/dist/neon/index.d.ts +0 -2
  74. package/dist/neon/index.js +0 -2
  75. package/dist/neon/neonQuerierPool.d.ts +2 -4
  76. package/dist/neon/neonQuerierPool.js +2 -6
  77. package/dist/pglite/index.d.ts +0 -1
  78. package/dist/pglite/index.js +0 -1
  79. package/dist/pglite/pgliteQuerier.d.ts +3 -3
  80. package/dist/pglite/pgliteQuerier.js +1 -1
  81. package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
  82. package/dist/pglite/pgliteQuerierPool.js +16 -6
  83. package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
  84. package/dist/postgres/abstractPgQuerierPool.js +8 -6
  85. package/dist/postgres/index.d.ts +0 -1
  86. package/dist/postgres/index.js +0 -1
  87. package/dist/postgres/pgNumericTypes.d.ts +5 -5
  88. package/dist/postgres/pgNumericTypes.js +11 -7
  89. package/dist/postgres/pgQuerier.d.ts +22 -4
  90. package/dist/postgres/pgQuerier.js +29 -2
  91. package/dist/postgres/pgQuerierPool.d.ts +2 -4
  92. package/dist/postgres/pgQuerierPool.js +2 -6
  93. package/dist/postgres/postgresDialect.d.ts +5 -5
  94. package/dist/postgres/postgresDialect.js +5 -5
  95. package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
  96. package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
  97. package/dist/querier/abstractPoolQuerier.d.ts +1 -1
  98. package/dist/querier/abstractPoolQuerier.js +1 -1
  99. package/dist/querier/abstractQuerier.js +17 -22
  100. package/dist/querier/abstractSqlQuerier.d.ts +21 -12
  101. package/dist/querier/abstractSqlQuerier.js +61 -43
  102. package/dist/schema/canonicalType.js +0 -2
  103. package/dist/schema/dependencyGraph.js +2 -4
  104. package/dist/schema/indexDifferences.js +2 -2
  105. package/dist/schema/schemaASTBuilder.js +3 -10
  106. package/dist/schema/schemaASTDiffer.d.ts +10 -2
  107. package/dist/schema/schemaASTDiffer.js +12 -11
  108. package/dist/schema/types.d.ts +1 -1
  109. package/dist/sqlite/hranaQuerier.d.ts +6 -8
  110. package/dist/sqlite/hranaQuerier.js +13 -30
  111. package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
  112. package/dist/sqlite/hranaQuerierPool.js +2 -1
  113. package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
  114. package/dist/sqlite/localSqliteQuerierPool.js +4 -2
  115. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
  116. package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
  117. package/dist/sqlite/sqliteDialect.js +2 -1
  118. package/dist/sqlite/sqlitePragmas.d.ts +12 -0
  119. package/dist/sqlite/sqlitePragmas.js +15 -0
  120. package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
  121. package/dist/sqlite/sqliteQuerierPool.js +2 -13
  122. package/dist/turso/index.d.ts +0 -1
  123. package/dist/turso/index.js +0 -1
  124. package/dist/turso/tursoLocalQuerier.d.ts +0 -1
  125. package/dist/turso/tursoLocalQuerierPool.js +2 -2
  126. package/dist/turso/tursoQuerierPool.d.ts +1 -3
  127. package/dist/turso/tursoQuerierPool.js +0 -4
  128. package/dist/type/dialect.d.ts +1 -1
  129. package/dist/util/dialect.util.d.ts +8 -2
  130. package/dist/util/dialect.util.js +20 -1
  131. package/dist/util/logger.d.ts +17 -9
  132. package/dist/util/logger.js +36 -11
  133. package/dist/util/object.util.d.ts +2 -0
  134. package/dist/util/object.util.js +4 -0
  135. package/dist/util/raw.js +3 -3
  136. package/dist/util/relationQuery.util.d.ts +3 -3
  137. package/dist/util/relationQuery.util.js +8 -2
  138. package/dist/util/sqlLiteral.js +3 -8
  139. package/dist/util/string.util.js +2 -6
  140. package/dist/util/wideNumber.d.ts +14 -0
  141. package/dist/util/wideNumber.js +24 -0
  142. package/package.json +1 -1
  143. package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
  144. package/dist/cockroachdb/crdbQuerier.js +0 -6
  145. package/dist/libsql/libsqlQuerier.d.ts +0 -10
  146. package/dist/libsql/libsqlQuerier.js +0 -10
  147. package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
  148. package/dist/mongo/mongodbNativeDialect.js +0 -9
  149. package/dist/mysql/mysql2Dialect.d.ts +0 -9
  150. package/dist/mysql/mysql2Dialect.js +0 -9
  151. package/dist/neon/neonDialect.d.ts +0 -10
  152. package/dist/neon/neonDialect.js +0 -10
  153. package/dist/neon/neonQuerier.d.ts +0 -5
  154. package/dist/neon/neonQuerier.js +0 -3
  155. package/dist/pglite/pgliteDialect.d.ts +0 -14
  156. package/dist/pglite/pgliteDialect.js +0 -14
  157. package/dist/postgres/abstractPgQuerier.d.ts +0 -24
  158. package/dist/postgres/abstractPgQuerier.js +0 -32
  159. package/dist/postgres/pgDialect.d.ts +0 -10
  160. package/dist/postgres/pgDialect.js +0 -10
  161. package/dist/turso/tursoQuerier.d.ts +0 -10
  162. package/dist/turso/tursoQuerier.js +0 -10
@@ -1,23 +1,22 @@
1
1
  import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
2
+ import { decodeBigInts } from '../util/wideNumber.js';
2
3
  export class MariadbQuerier extends AbstractPoolQuerier {
3
- constructor(connect, dialect, extra) {
4
- super(dialect, connect, extra);
5
- }
6
4
  async internalAll(query, values) {
7
- const res = await this.getConn().query(query, values);
8
- return res;
5
+ const rows = await this.getConn().query(query, values);
6
+ return Array.from(rows, decodeBigInts);
9
7
  }
10
8
  async internalRun(query, values) {
11
9
  const res = await this.getConn().query(query, values);
12
10
  // MariaDB may not set `affectedRows` when RETURNING is used; fall back to row count.
13
11
  const changes = res.affectedRows ?? res.length ?? 0;
14
- return this.buildUpdateResult({ rows: res.length ? res : [], changes, upsertStatus: res.affectedRows });
12
+ const rows = res.length ? Array.from(res, decodeBigInts) : [];
13
+ return this.buildUpdateResult({ rows, changes, upsertStatus: res.affectedRows });
15
14
  }
16
15
  async *internalStream(query, values) {
17
16
  const stream = this.getConn().queryStream(query, values);
18
17
  try {
19
18
  for await (const row of stream) {
20
- yield row;
19
+ yield decodeBigInts(row);
21
20
  }
22
21
  }
23
22
  finally {
@@ -8,19 +8,16 @@ export class MariadbQuerierPool extends AbstractSqlQuerierPool {
8
8
  pool;
9
9
  constructor(opts, extra) {
10
10
  super(new MariaDialect(dialectOptionsFrom(extra)), extra);
11
- // `mariadb` defaults to handing BIGINT back as a BigInt, and uql maps `type: Number` to BIGINT
12
- // (see `schema/canonicalType.ts`), so every auto-increment id reached a field declared `number`
13
- // as `9n` without this. Same trade as the pg pools: exact to 2^53, and `...opts` wins for a
14
- // caller who needs more. This belongs to the pool, not to the suites - it lived in
15
- // `mariadbQuerier.test.ts`, which meant the tests passed on behaviour the library never shipped.
16
- this.pool = createPool({ bigIntAsNumber: true, ...opts });
11
+ // BIGINT stays the driver's `bigint`, which `MariadbQuerier` decodes by the rule every driver here
12
+ // shares (`decodeWideNumber`) - not `bigIntAsNumber`, which rounds past 2^53 without a word.
13
+ this.pool = createPool(opts);
17
14
  // `mariadb`'s own `createPool` already attaches a silent no-op 'error'
18
15
  // listener (so a dropped connection can't crash the process), but its
19
16
  // `Pool` type only declares `on` for 'acquire' | 'connection' | 'enqueue'
20
17
  // | 'release' - 'error' genuinely fires at runtime (see `lib/pool.js`)
21
18
  // but isn't in the declaration, hence the cast. Re-attaching our own
22
19
  // listener here just makes the error visible instead of a silent no-op.
23
- attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error');
20
+ attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error', extra?.logger);
24
21
  }
25
22
  async getQuerier() {
26
23
  return new MariadbQuerier(() => this.pool.getConnection(), this.dialect, this.extra);
@@ -26,11 +26,11 @@ export function splitSqlStatements(sql) {
26
26
  const masterRegex = /'(?:''|\\['\\]|[^'])*(?:'|(?=$))|"(?:""|\\["\\]|[^"])*(?:"|(?=$))|`(?:``|\\[`\\]|[^`])*(?:`|(?=$))|\$(?<tag>[a-zA-Z0-9_]*)\$[\s\S]*?(?:\$\k<tag>|(?=$))|--.*|\/\*[\s\S]*?(?:\*\/|(?=$))|;/g;
27
27
  for (const match of sql.matchAll(masterRegex)) {
28
28
  if (match[0] === ';') {
29
- const stmt = sql.substring(lastIndex, match.index ?? 0).trim();
29
+ const stmt = sql.substring(lastIndex, match.index).trim();
30
30
  if (stmt) {
31
31
  statements.push(stmt);
32
32
  }
33
- lastIndex = (match.index ?? 0) + match[0].length;
33
+ lastIndex = match.index + match[0].length;
34
34
  }
35
35
  }
36
36
  const lastStmt = sql.substring(lastIndex).trim();
@@ -34,7 +34,6 @@ export async function main(args = process.argv.slice(2)) {
34
34
  try {
35
35
  const config = await loadConfig(customPath);
36
36
  assertCliConfig(config);
37
- const dialectName = config.pool.dialect.dialectName ?? 'postgres';
38
37
  const options = {
39
38
  migrationsPath: config.migrationsPath ?? './migrations',
40
39
  tableName: config.tableName,
@@ -43,13 +42,7 @@ export async function main(args = process.argv.slice(2)) {
43
42
  defaultForeignKeyAction: config.defaultForeignKeyAction,
44
43
  };
45
44
  const migrator = new Migrator(config.pool, options);
46
- if (!migrator.schemaGenerator) {
47
- const generator = await createSchemaGeneratorAsync(config.pool.dialect, config.defaultForeignKeyAction);
48
- if (!generator) {
49
- throw new TypeError(`Could not find a schema generator for dialect: ${dialectName}`);
50
- }
51
- migrator.setSchemaGenerator(generator);
52
- }
45
+ await migrator.ensureSchemaGenerator();
53
46
  switch (command) {
54
47
  case 'up':
55
48
  await runUp(migrator, filteredArgs.slice(1));
@@ -314,7 +307,7 @@ function printDriftGroup(title, drifts, icon, showSuggestion) {
314
307
  if (drift.expected && drift.actual) {
315
308
  console.log(` Expected: ${drift.expected}, Actual: ${drift.actual}`);
316
309
  }
317
- if (showSuggestion && drift.suggestion) {
310
+ if (showSuggestion) {
318
311
  console.log(` → ${drift.suggestion}`);
319
312
  }
320
313
  }
@@ -16,7 +16,7 @@ const OPTION_SOURCE = {
16
16
  type: (col) => {
17
17
  const columnType = canonicalToColumnType(col.type);
18
18
  return [
19
- ...(columnType ? [`columnType: ${quoted(columnType)}`] : []),
19
+ `columnType: ${quoted(columnType)}`,
20
20
  ...(col.type.length && col.type.category === 'string' ? [`length: ${col.type.length}`] : []),
21
21
  ...(col.type.precision === undefined ? [] : [`precision: ${col.type.precision}`]),
22
22
  ...(col.type.precision !== undefined && col.type.scale !== undefined ? [`scale: ${col.type.scale}`] : []),
@@ -7,9 +7,9 @@ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl
7
7
  export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
8
8
  export { TableDdl } from './tableDdl.js';
9
9
  /**
10
- * The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
11
- * dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
12
- * lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
10
+ * The index DDL a dialect gets, most specific first. `instanceof` rather than the `dialectName`
11
+ * {@link tableDdlFor} reads, because each family's index DDL is typed to its dialect and the narrowing
12
+ * is what hands it one. Anything else gets the portable form, which is SQLite's.
13
13
  */
14
14
  export declare function indexDdlFor(dialect: AbstractSqlDialect): IndexDdl;
15
15
  /**
@@ -14,9 +14,9 @@ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl
14
14
  export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
15
15
  export { TableDdl } from './tableDdl.js';
16
16
  /**
17
- * The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
18
- * dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
19
- * lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
17
+ * The index DDL a dialect gets, most specific first. `instanceof` rather than the `dialectName`
18
+ * {@link tableDdlFor} reads, because each family's index DDL is typed to its dialect and the narrowing
19
+ * is what hands it one. Anything else gets the portable form, which is SQLite's.
20
20
  */
21
21
  export function indexDdlFor(dialect) {
22
22
  if (dialect instanceof CockroachDialect) {
@@ -6,6 +6,11 @@ export declare class PgIndexDdl extends IndexDdl<PgLikeSqlDialect> {
6
6
  protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
7
7
  /** pgvector's own index types; CockroachDB's native one widens this. */
8
8
  protected isVectorIndex(index: IndexSchema): boolean;
9
+ /**
10
+ * ` USING <method>`. `fulltext` is refused rather than compiled into a ` USING fulltext` the server
11
+ * can only answer with a syntax error: `$text` computes its `TO_TSVECTOR` per row, which no index
12
+ * over the raw columns serves.
13
+ */
9
14
  protected indexAccessMethod(index: IndexSchema): string;
10
15
  /**
11
16
  * A vector index's operator class is named `{type}_{metric}_ops`: an index on a `halfvec` column
@@ -14,7 +14,16 @@ export class PgIndexDdl extends IndexDdl {
14
14
  isVectorIndex(index) {
15
15
  return index.type === 'hnsw' || index.type === 'ivfflat';
16
16
  }
17
+ /**
18
+ * ` USING <method>`. `fulltext` is refused rather than compiled into a ` USING fulltext` the server
19
+ * can only answer with a syntax error: `$text` computes its `TO_TSVECTOR` per row, which no index
20
+ * over the raw columns serves.
21
+ */
17
22
  indexAccessMethod(index) {
23
+ if (index.type === 'fulltext') {
24
+ throw new TypeError(`${this.dialect.dialectName} has no fulltext index (index "${index.name}"). $text needs none there; ` +
25
+ 'name the columns it searches with $fields.');
26
+ }
18
27
  return index.type ? ` USING ${index.type}` : '';
19
28
  }
20
29
  /**
@@ -5,7 +5,7 @@
5
5
  * actual database schema.
6
6
  */
7
7
  import { canonicalToSql, engineType } from '../../schema/canonicalType.js';
8
- import { diffSchemas } from '../../schema/schemaASTDiffer.js';
8
+ import { diffSchemas, referentialActions } from '../../schema/schemaASTDiffer.js';
9
9
  function resolveOptions(options) {
10
10
  return {
11
11
  checkTypes: options.checkTypes ?? true,
@@ -114,7 +114,7 @@ function detectColumnDrifts(diff, opts) {
114
114
  suggestion: 'Add to entity or create migration to drop',
115
115
  });
116
116
  }
117
- else if (colDiff.type === 'alter') {
117
+ else {
118
118
  addAlterColumnDrifts(colDiff, drifts, opts);
119
119
  }
120
120
  }
@@ -124,10 +124,6 @@ function detectColumnDrifts(diff, opts) {
124
124
  * Add drifts for column alterations (type/nullable mismatches).
125
125
  */
126
126
  function addAlterColumnDrifts(colDiff, drifts, opts) {
127
- // Every check below compares the two sides, so there is nothing to report without both.
128
- if (!colDiff.expected || !colDiff.actual) {
129
- return;
130
- }
131
127
  // An auto-increment key is created through the dialect's `serialPrimaryKey`, whose spelling the
132
128
  // entity never states - `BIGINT UNSIGNED AUTO_INCREMENT` on the MySQL family, where the column then
133
129
  // reads back as `BIGINT UNSIGNED` against an entity that can only say `BIGINT`. Comparing the two
@@ -206,7 +202,7 @@ function detectIndexDrifts(diff) {
206
202
  suggestion: 'Add @Field({ index }) or create migration to drop',
207
203
  });
208
204
  }
209
- else if (idxDiff.type === 'alter') {
205
+ else {
210
206
  // No `expected`/`actual` here: the CLI prints those by interpolation, where an `IndexNode`
211
207
  // renders as `[object Object]`. What differs is already spelled out in `description`.
212
208
  drifts.push({
@@ -247,9 +243,26 @@ function detectRelationshipDrifts(diff) {
247
243
  suggestion: 'Add relation to entity or drop FK',
248
244
  });
249
245
  }
246
+ else {
247
+ drifts.push({
248
+ type: 'relationship_mismatch',
249
+ severity: 'warning',
250
+ table: relDiff.fromTable,
251
+ relationship: relDiff.name,
252
+ expected: formatActions(relDiff.expected),
253
+ actual: formatActions(relDiff.actual),
254
+ details: `FK "${relDiff.name}" has other referential actions in the database than in the entity`,
255
+ suggestion: 'Generate a migration, which drops and re-adds the constraint',
256
+ });
257
+ }
250
258
  }
251
259
  return drifts;
252
260
  }
261
+ /** Both actions spelled out, so a side that left one unstated reads the same as one that stated the default. */
262
+ function formatActions(rel) {
263
+ const { onDelete, onUpdate } = referentialActions(rel);
264
+ return `ON DELETE ${onDelete} ON UPDATE ${onUpdate}`;
265
+ }
253
266
  /**
254
267
  * Calculate overall status based on drifts.
255
268
  */
@@ -275,7 +288,7 @@ function createSummary(drifts) {
275
288
  * Format type for display.
276
289
  */
277
290
  function formatType(type, dialect) {
278
- if (!type || !dialect)
291
+ if (!dialect)
279
292
  return 'unknown';
280
293
  return canonicalToSql(type, dialect);
281
294
  }
@@ -147,7 +147,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
147
147
  if (!currentTable) {
148
148
  return { tableName: collectionName, type: 'create' };
149
149
  }
150
- const existingIndexes = new Set(currentTable.indexes?.map((i) => i.name) ?? []);
150
+ const existingIndexes = new Set(currentTable.indexes.map((i) => i.name));
151
151
  const indexesToAdd = this.fieldIndexes(meta, collectionName).filter((index) => !existingIndexes.has(index.name));
152
152
  if (indexesToAdd.length === 0) {
153
153
  return undefined;
@@ -29,7 +29,7 @@ export declare abstract class BaseSqlIntrospector {
29
29
  * Build SchemaAST from table schemas.
30
30
  */
31
31
  protected buildAST(tableSchemas: TableSchema[]): SchemaAST;
32
- private buildTables;
32
+ private buildTable;
33
33
  private buildRelationships;
34
34
  private buildIndexes;
35
35
  }
@@ -43,90 +43,79 @@ export class BaseSqlIntrospector {
43
43
  */
44
44
  buildAST(tableSchemas) {
45
45
  const ast = new SchemaAST();
46
- const tableNodes = new Map();
47
- this.buildTables(ast, tableNodes, tableSchemas);
48
- this.buildRelationships(ast, tableNodes, tableSchemas);
49
- this.buildIndexes(ast, tableNodes, tableSchemas);
46
+ // Every table first, so a foreign key reaches a table introspected after the one it leaves from.
47
+ const tables = tableSchemas.map((schema) => ({ schema, table: this.buildTable(schema) }));
48
+ const tableNodes = new Map(tables.map(({ schema, table }) => [schema.name, table]));
49
+ for (const { table } of tables) {
50
+ ast.addTable(table);
51
+ }
52
+ for (const { schema, table } of tables) {
53
+ this.buildRelationships(ast, tableNodes, schema, table);
54
+ this.buildIndexes(ast, schema, table);
55
+ }
50
56
  return ast;
51
57
  }
52
- buildTables(ast, tableNodes, tableSchemas) {
53
- for (const schema of tableSchemas) {
54
- const table = createTableNode(schema.name, this.schema);
55
- const { columns } = table;
56
- for (const col of schema.columns) {
57
- // Spread, not field by field: a `ColumnSchema` is a `ColumnNode` minus the graph links, so
58
- // everything but the type crosses unchanged and a field either shape gains cannot be dropped
59
- // here. Listed by hand this had already lost `enum` and `generatedAs`.
60
- const { type, length: _length, precision: _precision, scale: _scale, ...rest } = col;
61
- const column = {
62
- ...rest,
63
- type: canonicalColumnType(type, col),
64
- table,
65
- referencedBy: [],
66
- };
67
- columns.set(col.name, column);
68
- }
69
- // From the ordered list the query returned, not from the per-column flags: `(a, b)` is a
70
- // different key from `(b, a)`, and a flag says only that a column is *in* the key. Falls back
71
- // to the flags for an introspector that reports no key of its own.
72
- const keyColumns = schema.primaryKey ?? schema.columns.filter((col) => col.isPrimaryKey).map((col) => col.name);
73
- table.primaryKey.push(...keyColumns.flatMap((name) => columns.get(name) ?? []));
74
- table.primaryKeyName = schema.primaryKeyName;
75
- tableNodes.set(schema.name, table);
76
- ast.addTable(table);
58
+ buildTable(schema) {
59
+ const table = createTableNode(schema.name, this.schema);
60
+ const { columns } = table;
61
+ for (const col of schema.columns) {
62
+ // Spread, not field by field: a `ColumnSchema` is a `ColumnNode` minus the graph links, so
63
+ // everything but the type crosses unchanged and a field either shape gains cannot be dropped
64
+ // here. Listed by hand this had already lost `enum` and `generatedAs`.
65
+ const { type, length: _length, precision: _precision, scale: _scale, ...rest } = col;
66
+ const column = {
67
+ ...rest,
68
+ type: canonicalColumnType(type, col),
69
+ table,
70
+ referencedBy: [],
71
+ };
72
+ columns.set(col.name, column);
77
73
  }
74
+ // From the ordered list the query returned, not from the per-column flags: `(a, b)` is a
75
+ // different key from `(b, a)`, and a flag says only that a column is *in* the key. Falls back
76
+ // to the flags for an introspector that reports no key of its own.
77
+ const keyColumns = schema.primaryKey ?? schema.columns.filter((col) => col.isPrimaryKey).map((col) => col.name);
78
+ table.primaryKey.push(...keyColumns.flatMap((name) => columns.get(name) ?? []));
79
+ table.primaryKeyName = schema.primaryKeyName;
80
+ return table;
78
81
  }
79
- buildRelationships(ast, tableNodes, tableSchemas) {
80
- for (const schema of tableSchemas) {
81
- if (!schema.foreignKeys)
82
+ buildRelationships(ast, tableNodes, schema, fromTable) {
83
+ for (const fk of schema.foreignKeys ?? []) {
84
+ const toTable = tableNodes.get(fk.references.table);
85
+ if (!toTable)
82
86
  continue;
83
- const fromTable = tableNodes.get(schema.name);
84
- if (!fromTable)
85
- continue;
86
- for (const fk of schema.foreignKeys) {
87
- const toTable = tableNodes.get(fk.references.table);
88
- if (!toTable)
89
- continue;
90
- const fromColumns = fk.columns.flatMap((name) => fromTable.columns.get(name) ?? []);
91
- const toColumns = fk.references.columns.flatMap((name) => toTable.columns.get(name) ?? []);
92
- if (fromColumns.length > 0 && toColumns.length > 0) {
93
- const rel = {
94
- name: fk.name ?? derivedForeignKeyName(schema.name, fk.columns),
95
- type: fromColumns[0].isUnique ? 'OneToOne' : 'ManyToOne',
96
- from: { table: fromTable, columns: fromColumns },
97
- to: { table: toTable, columns: toColumns },
98
- onDelete: fk.onDelete || 'NO ACTION',
99
- onUpdate: fk.onUpdate || 'NO ACTION',
100
- };
101
- ast.addRelationship(rel);
102
- }
87
+ const fromColumns = fk.columns.flatMap((name) => fromTable.columns.get(name) ?? []);
88
+ const toColumns = fk.references.columns.flatMap((name) => toTable.columns.get(name) ?? []);
89
+ if (fromColumns.length > 0 && toColumns.length > 0) {
90
+ const rel = {
91
+ name: fk.name ?? derivedForeignKeyName(schema.name, fk.columns),
92
+ type: fromColumns[0].isUnique ? 'OneToOne' : 'ManyToOne',
93
+ from: { table: fromTable, columns: fromColumns },
94
+ to: { table: toTable, columns: toColumns },
95
+ onDelete: fk.onDelete || 'NO ACTION',
96
+ onUpdate: fk.onUpdate || 'NO ACTION',
97
+ };
98
+ ast.addRelationship(rel);
103
99
  }
104
100
  }
105
101
  }
106
- buildIndexes(ast, tableNodes, tableSchemas) {
107
- for (const schema of tableSchemas) {
108
- if (!schema.indexes)
109
- continue;
110
- const table = tableNodes.get(schema.name);
111
- if (!table)
112
- continue;
113
- for (const idx of schema.indexes) {
114
- // An expression has no column to resolve. Dropping the entries that name a column this table
115
- // does not have, and the index if that leaves none, is what the entity side does too.
116
- const entries = idx.entries.filter((entry) => entry.expression || table.columns.has(entry.column));
117
- if (entries.length > 0) {
118
- const index = {
119
- name: idx.name,
120
- table,
121
- entries,
122
- unique: idx.unique,
123
- type: idx.type,
124
- where: idx.where,
125
- include: idx.include,
126
- source: 'database',
127
- };
128
- ast.addIndex(index);
129
- }
102
+ buildIndexes(ast, schema, table) {
103
+ for (const idx of schema.indexes ?? []) {
104
+ // An expression has no column to resolve. Dropping the entries that name a column this table
105
+ // does not have, and the index if that leaves none, is what the entity side does too.
106
+ const entries = idx.entries.filter((entry) => entry.expression || table.columns.has(entry.column));
107
+ if (entries.length > 0) {
108
+ const index = {
109
+ name: idx.name,
110
+ table,
111
+ entries,
112
+ unique: idx.unique,
113
+ type: idx.type,
114
+ where: idx.where,
115
+ include: idx.include,
116
+ source: 'database',
117
+ };
118
+ ast.addIndex(index);
130
119
  }
131
120
  }
132
121
  }
@@ -27,7 +27,8 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
27
27
  `;
28
28
  }
29
29
  parseTableExistsResult(results) {
30
- return (this.toNumber(results[0]?.count) ?? 0) > 0;
30
+ // No row, or no count in it, reads as `NaN`, which is not above zero.
31
+ return Number(results[0]?.count) > 0;
31
32
  }
32
33
  /**
33
34
  * From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of
@@ -24,11 +24,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
24
24
  `;
25
25
  }
26
26
  parseTableExistsResult(results) {
27
- const row = results[0];
28
- if (row?.count !== undefined) {
29
- return (this.toNumber(row.count) ?? 0) > 0;
30
- }
31
- return false;
27
+ // No row, or no count in it, reads as `NaN`, which is not above zero.
28
+ return Number(results[0]?.count) > 0;
32
29
  }
33
30
  getColumnsQuery(_tableName) {
34
31
  return /*sql*/ `
@@ -97,7 +94,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
97
94
  async mapColumnsResult(_read, _tableName, results) {
98
95
  return results.map((row) => ({
99
96
  name: row.column_name,
100
- type: (row.column_type || '').toUpperCase(),
97
+ type: row.column_type.toUpperCase(),
101
98
  nullable: row.is_nullable === 'YES',
102
99
  defaultValue: this.parseDefaultValue(row.column_default),
103
100
  isPrimaryKey: row.column_key === 'PRI',
@@ -122,8 +119,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
122
119
  async mapForeignKeysResult(_read, _tableName, results) {
123
120
  return results.map((row) => ({
124
121
  name: row.constraint_name,
125
- columns: (row.columns || '').split(','),
126
- references: { table: row.referenced_table, columns: (row.referenced_columns || '').split(',') },
122
+ columns: row.columns.split(','),
123
+ references: { table: row.referenced_table, columns: row.referenced_columns.split(',') },
127
124
  onDelete: this.normalizeReferentialAction(row.delete_rule),
128
125
  onUpdate: this.normalizeReferentialAction(row.update_rule),
129
126
  }));
@@ -22,11 +22,8 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
22
22
  `;
23
23
  }
24
24
  parseTableExistsResult(results) {
25
- const row = results[0];
26
- if (row?.count !== undefined) {
27
- return (this.toNumber(row.count) ?? 0) > 0;
28
- }
29
- return false;
25
+ // No row, or no count in it, reads as `NaN`, which is not above zero.
26
+ return Number(results[0]?.count) > 0;
30
27
  }
31
28
  /**
32
29
  * `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
@@ -19,8 +19,11 @@ export declare class Migrator {
19
19
  private readonly _defaultForeignKeyAction?;
20
20
  private _mongoSchemaLoadPromise?;
21
21
  constructor(pool: QuerierPool, options?: MigratorOptions);
22
- /** Loads MongoDB schema generator on first use; SQL generators are set in the constructor (or via {@link setSchemaGenerator}). */
23
- private ensureSchemaGenerator;
22
+ /**
23
+ * Loads MongoDB's schema generator on first use, so the optional `mongodb` peer loads only then. SQL
24
+ * generators are set in the constructor (or via {@link setSchemaGenerator}).
25
+ */
26
+ ensureSchemaGenerator(): Promise<void>;
24
27
  /**
25
28
  * Set the schema generator for DDL operations
26
29
  */
@@ -10,7 +10,6 @@ import { buildSqlQuerierMigrationModule, EMPTY_MANUAL_MIGRATION_DOWN_INNER, EMPT
10
10
  import { runMongoCommand } from './generator/mongoCommand.js';
11
11
  import { introspectorFor } from './introspection/registry.js';
12
12
  import { createSchemaGenerator } from './schemaGenerator.js';
13
- import { createSchemaGeneratorAsync } from './schemaGeneratorAsync.js';
14
13
  import { DatabaseMigrationStorage } from './storage/databaseStorage.js';
15
14
  /**
16
15
  * Main class for managing database migrations
@@ -37,7 +36,7 @@ export class Migrator {
37
36
  _mongoSchemaLoadPromise;
38
37
  constructor(pool, options = {}) {
39
38
  this.pool = pool;
40
- this.dialectName = pool.dialect.dialectName ?? 'postgres';
39
+ this.dialectName = pool.dialect.dialectName;
41
40
  this._defaultForeignKeyAction = options.defaultForeignKeyAction;
42
41
  this.storage =
43
42
  options.storage ??
@@ -51,18 +50,17 @@ export class Migrator {
51
50
  this.schemaGenerator =
52
51
  options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
53
52
  }
54
- /** Loads MongoDB schema generator on first use; SQL generators are set in the constructor (or via {@link setSchemaGenerator}). */
53
+ /**
54
+ * Loads MongoDB's schema generator on first use, so the optional `mongodb` peer loads only then. SQL
55
+ * generators are set in the constructor (or via {@link setSchemaGenerator}).
56
+ */
55
57
  async ensureSchemaGenerator() {
56
58
  if (this.schemaGenerator || this.dialectName !== 'mongodb') {
57
59
  return;
58
60
  }
59
- if (!this._mongoSchemaLoadPromise) {
60
- this._mongoSchemaLoadPromise = createSchemaGeneratorAsync(this.pool.dialect, this._defaultForeignKeyAction).then((gen) => {
61
- if (gen) {
62
- this.schemaGenerator = gen;
63
- }
64
- });
65
- }
61
+ this._mongoSchemaLoadPromise ??= import('./generator/mongoSchemaGenerator.js').then(({ MongoSchemaGenerator }) => {
62
+ this.schemaGenerator = new MongoSchemaGenerator(this.pool.dialect.namingStrategy, this._defaultForeignKeyAction);
63
+ });
66
64
  await this._mongoSchemaLoadPromise;
67
65
  }
68
66
  /**
@@ -302,12 +300,9 @@ export class Migrator {
302
300
  }
303
301
  async findEntityForTable(tableName) {
304
302
  await this.ensureSchemaGenerator();
305
- if (!this.schemaGenerator) {
306
- return undefined;
307
- }
308
303
  for (const entity of this.entities) {
309
304
  const meta = getMeta(entity);
310
- const name = this.schemaGenerator.resolveTableName(meta);
305
+ const name = this.generator.resolveTableName(meta);
311
306
  if (name === tableName) {
312
307
  return entity;
313
308
  }
@@ -474,8 +474,6 @@ export class SqlSchemaGenerator {
474
474
  if (current == null || desired == null)
475
475
  return current == null && desired == null;
476
476
  const normalize = (value) => {
477
- if (value === null)
478
- return 'null';
479
477
  // Render first: the desired side may be a symbolic expression, the current side is always the
480
478
  // engine's own text, and `{"kind":"now"}` matches no spelling of `CURRENT_TIMESTAMP`.
481
479
  const val = SqlExpression.isExpression(value) ? formatDefaultValue(value, this.dialect) : value;
@@ -518,10 +516,8 @@ export class SqlSchemaGenerator {
518
516
  constraints.push(`CONSTRAINT ${this.escapeId(name)} CHECK (${check.expression})`);
519
517
  });
520
518
  for (const rel of table.outgoingRelations) {
521
- if (rel.from.columns.length > 0) {
522
- const refTable = this.dialect.escapeQualifiedId(rel.to.table.name, rel.to.table.schema);
523
- constraints.push(this.foreignKeyConstraint(table.name, foreignKeyOf(rel), refTable));
524
- }
519
+ const refTable = this.dialect.escapeQualifiedId(rel.to.table.name, rel.to.table.schema);
520
+ constraints.push(this.foreignKeyConstraint(table.name, foreignKeyOf(rel), refTable));
525
521
  }
526
522
  const ifNotExists = options.ifNotExists && this.features.ifNotExists ? 'IF NOT EXISTS ' : '';
527
523
  let createSql = `CREATE TABLE ${ifNotExists}${this.dialect.escapeQualifiedId(table.name, table.schema)} (\n`;
@@ -1,5 +1,4 @@
1
1
  export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.js';
2
2
  export * from './mongoDialect.js';
3
- export * from './mongodbNativeDialect.js';
4
3
  export * from './mongodbQuerier.js';
5
4
  export * from './mongodbQuerierPool.js';
@@ -1,5 +1,4 @@
1
1
  export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.js';
2
2
  export * from './mongoDialect.js';
3
- export * from './mongodbNativeDialect.js';
4
3
  export * from './mongodbQuerier.js';
5
4
  export * from './mongodbQuerierPool.js';
@@ -20,7 +20,6 @@ export declare class MongoDialect extends AbstractDialect {
20
20
  private static readonly ID_KEY;
21
21
  /** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
22
22
  private static readonly MAX_NUM_CANDIDATES;
23
- private static readonly AGGREGATE_OP_MAP;
24
23
  /**
25
24
  * MongoDB stores the primary key as `_id`; everything else resolves as usual. Projections, sorts,
26
25
  * `$group` refs and `$where` keys all map through here, so no read path can address a property name
@@ -188,7 +187,7 @@ export declare class MongoDialect extends AbstractDialect {
188
187
  /** `[column, key]` for the fields whose stored name differs from their property name, memoized per entity. */
189
188
  private renamedColumns;
190
189
  private referenceKeys;
191
- normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[] | undefined): E[] | undefined;
190
+ normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[]): E[];
192
191
  /** `doc` is the wire shape - `_id`, stored names, `ObjectId`s - and what comes back is the code's. */
193
192
  normalizeId<E extends Document>(meta: EntityMeta<E>, doc: Document | undefined): E | undefined;
194
193
  /**