uql-orm 0.21.0 → 0.22.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 (142) hide show
  1. package/README.md +5 -4
  2. package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
  3. package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
  4. package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
  5. package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
  6. package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
  7. package/dist/cockroachdb/cockroachDialect.js +33 -18
  8. package/dist/d1/d1Querier.d.ts +4 -3
  9. package/dist/d1/d1Querier.js +0 -3
  10. package/dist/d1/d1SqliteDialect.d.ts +7 -0
  11. package/dist/d1/d1SqliteDialect.js +9 -0
  12. package/dist/dialect/abstractDialect.d.ts +8 -3
  13. package/dist/dialect/abstractDialect.js +9 -4
  14. package/dist/dialect/abstractSqlDialect.d.ts +35 -7
  15. package/dist/dialect/abstractSqlDialect.js +76 -35
  16. package/dist/dialect/index.d.ts +1 -0
  17. package/dist/dialect/index.js +1 -0
  18. package/dist/dialect/indexSqlDialect.d.ts +57 -0
  19. package/dist/dialect/indexSqlDialect.js +101 -0
  20. package/dist/dialect/mysqlLikeSqlDialect.d.ts +16 -1
  21. package/dist/dialect/mysqlLikeSqlDialect.js +32 -1
  22. package/dist/dialect/pgLikeSqlDialect.d.ts +33 -15
  23. package/dist/dialect/pgLikeSqlDialect.js +90 -56
  24. package/dist/dialect/vectorCast.d.ts +21 -0
  25. package/dist/dialect/vectorCast.js +28 -0
  26. package/dist/dialect/vectorSqlDialect.d.ts +10 -3
  27. package/dist/dialect/vectorSqlDialect.js +19 -8
  28. package/dist/entity/decorator/index-decorator.d.ts +2 -9
  29. package/dist/entity/metadata/definition.d.ts +5 -4
  30. package/dist/entity/metadata/definition.js +4 -3
  31. package/dist/libsql/libsqlDialect.d.ts +12 -2
  32. package/dist/libsql/libsqlDialect.js +14 -2
  33. package/dist/libsql/libsqlQuerier.d.ts +9 -22
  34. package/dist/libsql/libsqlQuerier.js +9 -85
  35. package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
  36. package/dist/libsql/libsqlQuerierPool.js +17 -15
  37. package/dist/maria/mariaDialect.d.ts +26 -4
  38. package/dist/maria/mariaDialect.js +54 -15
  39. package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
  40. package/dist/migrate/builder/migrationBuilder.js +58 -76
  41. package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
  42. package/dist/migrate/builder/splitSqlStatements.js +0 -2
  43. package/dist/migrate/builder/tableBuilder.d.ts +12 -2
  44. package/dist/migrate/builder/tableBuilder.js +71 -119
  45. package/dist/migrate/builder/types.d.ts +30 -41
  46. package/dist/migrate/cli.d.ts +3 -2
  47. package/dist/migrate/cli.js +21 -48
  48. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
  49. package/dist/migrate/codegen/entityCodeGenerator.js +5 -63
  50. package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
  51. package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
  52. package/dist/migrate/codegen/index.d.ts +0 -2
  53. package/dist/migrate/codegen/index.js +0 -4
  54. package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
  55. package/dist/migrate/generator/indexNodeToSchema.js +14 -0
  56. package/dist/migrate/generator/mongoCommand.d.ts +57 -0
  57. package/dist/migrate/generator/mongoCommand.js +26 -0
  58. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
  59. package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
  60. package/dist/migrate/index.d.ts +0 -1
  61. package/dist/migrate/index.js +0 -1
  62. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +25 -10
  63. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +46 -32
  64. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
  65. package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
  66. package/dist/migrate/introspection/mongoIntrospector.js +2 -2
  67. package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
  68. package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
  69. package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
  70. package/dist/migrate/introspection/postgresIntrospector.js +4 -10
  71. package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
  72. package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
  73. package/dist/migrate/migrator.d.ts +23 -3
  74. package/dist/migrate/migrator.js +57 -69
  75. package/dist/migrate/schemaGenerator.d.ts +26 -37
  76. package/dist/migrate/schemaGenerator.js +69 -174
  77. package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
  78. package/dist/migrate/schemaGeneratorAsync.js +3 -3
  79. package/dist/migrate/storage/databaseStorage.js +4 -4
  80. package/dist/mongo/mongoDialect.d.ts +2 -0
  81. package/dist/mongo/mongoDialect.js +14 -3
  82. package/dist/mysql/mysqlDialect.d.ts +8 -0
  83. package/dist/mysql/mysqlDialect.js +13 -0
  84. package/dist/schema/canonicalType.d.ts +14 -2
  85. package/dist/schema/canonicalType.js +41 -31
  86. package/dist/schema/index.d.ts +1 -1
  87. package/dist/schema/schemaASTBuilder.d.ts +8 -1
  88. package/dist/schema/schemaASTBuilder.js +56 -47
  89. package/dist/schema/types.d.ts +10 -37
  90. package/dist/sqlite/abstractSqliteQuerier.d.ts +39 -0
  91. package/dist/sqlite/abstractSqliteQuerier.js +44 -0
  92. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
  93. package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
  94. package/dist/sqlite/hranaQuerier.d.ts +54 -0
  95. package/dist/sqlite/hranaQuerier.js +90 -0
  96. package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
  97. package/dist/sqlite/hranaQuerierPool.js +25 -0
  98. package/dist/sqlite/index.d.ts +2 -1
  99. package/dist/sqlite/index.js +2 -1
  100. package/dist/sqlite/sqliteDialect.d.ts +18 -4
  101. package/dist/sqlite/sqliteDialect.js +24 -24
  102. package/dist/sqlite/sqliteQuerier.d.ts +16 -9
  103. package/dist/sqlite/sqliteQuerier.js +4 -27
  104. package/dist/sqlite/sqliteQuerierPool.d.ts +14 -4
  105. package/dist/sqlite/sqliteQuerierPool.js +16 -5
  106. package/dist/turso/index.d.ts +3 -0
  107. package/dist/turso/index.js +3 -0
  108. package/dist/turso/local.d.ts +3 -0
  109. package/dist/turso/local.js +3 -0
  110. package/dist/turso/tursoDialect.d.ts +15 -0
  111. package/dist/turso/tursoDialect.js +18 -0
  112. package/dist/turso/tursoLocalQuerier.d.ts +25 -0
  113. package/dist/turso/tursoLocalQuerier.js +20 -0
  114. package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
  115. package/dist/turso/tursoLocalQuerierPool.js +39 -0
  116. package/dist/turso/tursoQuerier.d.ts +10 -0
  117. package/dist/turso/tursoQuerier.js +10 -0
  118. package/dist/turso/tursoQuerierPool.d.ts +39 -0
  119. package/dist/turso/tursoQuerierPool.js +38 -0
  120. package/dist/type/config.d.ts +0 -6
  121. package/dist/type/dialect.d.ts +12 -4
  122. package/dist/type/dialect.js +7 -1
  123. package/dist/type/entity.d.ts +66 -5
  124. package/dist/type/migration.d.ts +32 -23
  125. package/dist/type/vector.d.ts +5 -2
  126. package/dist/util/index.d.ts +1 -0
  127. package/dist/util/index.js +1 -0
  128. package/dist/util/indexColumn.util.d.ts +6 -0
  129. package/dist/util/indexColumn.util.js +26 -0
  130. package/package.json +16 -3
  131. package/dist/migrate/codegen/entityMerger.d.ts +0 -114
  132. package/dist/migrate/codegen/entityMerger.js +0 -293
  133. package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
  134. package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
  135. package/dist/migrate/generator/index.d.ts +0 -2
  136. package/dist/migrate/generator/index.js +0 -2
  137. package/dist/migrate/sync/index.d.ts +0 -6
  138. package/dist/migrate/sync/index.js +0 -6
  139. package/dist/migrate/sync/schemaSync.d.ts +0 -131
  140. package/dist/migrate/sync/schemaSync.js +0 -259
  141. package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
  142. package/dist/sqlite/betterSqlite3Dialect.js +0 -9
@@ -32,16 +32,16 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
32
32
  }
33
33
  // SQLite uses PRAGMA which doesn't use parameterized queries in the same way
34
34
  getColumnsQuery(tableName) {
35
- return `PRAGMA table_info(${this.escapeId(tableName)})`;
35
+ return /*sql*/ `PRAGMA table_info(${this.escapeId(tableName)})`;
36
36
  }
37
37
  getIndexesQuery(tableName) {
38
- return `PRAGMA index_list(${this.escapeId(tableName)})`;
38
+ return /*sql*/ `PRAGMA index_list(${this.escapeId(tableName)})`;
39
39
  }
40
40
  getForeignKeysQuery(tableName) {
41
- return `PRAGMA foreign_key_list(${this.escapeId(tableName)})`;
41
+ return /*sql*/ `PRAGMA foreign_key_list(${this.escapeId(tableName)})`;
42
42
  }
43
43
  getPrimaryKeyQuery(tableName) {
44
- return `PRAGMA table_info(${this.escapeId(tableName)})`;
44
+ return /*sql*/ `PRAGMA table_info(${this.escapeId(tableName)})`;
45
45
  }
46
46
  getColumnsParams(_tableName) {
47
47
  return [];
@@ -61,9 +61,9 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
61
61
  mapTableNameRow(row) {
62
62
  return row.name;
63
63
  }
64
- async mapColumnsResult(querier, tableName, results) {
64
+ async mapColumnsResult(read, tableName, results) {
65
65
  // Get unique columns from indexes
66
- const uniqueColumns = await this.getUniqueColumns(querier, tableName);
66
+ const uniqueColumns = await this.getUniqueColumns(read, tableName);
67
67
  return results.map((row) => ({
68
68
  name: row.name,
69
69
  type: this.normalizeType(row.type),
@@ -78,25 +78,29 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
78
78
  comment: undefined, // SQLite doesn't support column comments
79
79
  }));
80
80
  }
81
- async mapIndexesResult(querier, _tableName, results) {
81
+ async mapIndexesResult(read, _tableName, results) {
82
82
  const indexSchemas = [];
83
83
  for (const index of results) {
84
- const columns = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
84
+ const columns = await this.getIndexColumns(read, index.name);
85
85
  // Include user-created indexes ('c') and multi-column unique constraints ('u')
86
86
  // Skip primary key indexes ('pk') and single-column unique constraints
87
87
  const isUserCreated = index.origin === 'c';
88
88
  const isCompositeUnique = index.origin === 'u' && columns.length > 1;
89
- if (isUserCreated || isCompositeUnique) {
89
+ // `PRAGMA index_info` names an expression entry `null` (its `cid` is -2), and the expression text
90
+ // lives only in `sqlite_master.sql`. Reporting `{ column: null }` put a column literally named
91
+ // `null` into the diff, so an index UQL cannot describe is left out entirely instead.
92
+ const named = columns.filter((column) => column.name !== null);
93
+ if (named.length === columns.length && (isUserCreated || isCompositeUnique)) {
90
94
  indexSchemas.push({
91
95
  name: index.name,
92
- columns: columns.map((c) => c.name),
96
+ columns: named.map((column) => ({ column: column.name })),
93
97
  unique: Boolean(index.unique),
94
98
  });
95
99
  }
96
100
  }
97
101
  return indexSchemas;
98
102
  }
99
- async mapForeignKeysResult(_querier, tableName, results) {
103
+ async mapForeignKeysResult(_read, tableName, results) {
100
104
  // Group by id to handle composite foreign keys
101
105
  const grouped = new Map();
102
106
  for (const row of results) {
@@ -127,20 +131,24 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
127
131
  // ============================================================================
128
132
  // SQLite-specific helpers
129
133
  // ============================================================================
130
- async getUniqueColumns(querier, tableName) {
131
- const results = await querier.all(`PRAGMA index_list(${this.escapeId(tableName)})`);
134
+ async getUniqueColumns(read, tableName) {
135
+ const indexes = await read(this.getIndexesQuery(tableName));
132
136
  const uniqueColumns = new Set();
133
- for (const index of results) {
137
+ for (const index of indexes) {
134
138
  if (index.unique) {
135
- const indexInfo = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
136
- // Only single-column unique constraints
137
- if (indexInfo.length === 1) {
138
- uniqueColumns.add(indexInfo[0].name);
139
+ const columns = await this.getIndexColumns(read, index.name);
140
+ // Only single-column unique constraints, and only over a real column (not an expression)
141
+ const [column] = columns;
142
+ if (columns.length === 1 && column.name !== null) {
143
+ uniqueColumns.add(column.name);
139
144
  }
140
145
  }
141
146
  }
142
147
  return uniqueColumns;
143
148
  }
149
+ getIndexColumns(read, indexName) {
150
+ return read(/*sql*/ `PRAGMA index_info(${this.escapeId(indexName)})`);
151
+ }
144
152
  normalizeType(type) {
145
153
  // Extract base type without length/precision
146
154
  const match = type.match(/^([A-Za-z]+)/);
@@ -1,4 +1,4 @@
1
- import type { DialectName, LoggingOptions, Migration, MigrationDefinition, MigrationResult, MigrationStorage, MigratorOptions, MongoQuerier, NamingStrategy, Querier, QuerierPool, SchemaDiff, SchemaGenerator, SchemaIntrospector, Type } from '../type/index.js';
1
+ import type { DialectName, LoggingOptions, Migration, MigrationDefinition, MigrationResult, MigrationStorage, MigratorOptions, MongoQuerier, Querier, QuerierPool, SchemaDiff, SchemaGenerator, SchemaIntrospector, Type } from '../type/index.js';
2
2
  import { LoggerWrapper } from '../util/index.js';
3
3
  import type { IMigrationBuilder } from './builder/types.js';
4
4
  /**
@@ -16,7 +16,7 @@ export declare class Migrator {
16
16
  readonly dialectName: DialectName;
17
17
  schemaGenerator?: SchemaGenerator;
18
18
  schemaIntrospector?: SchemaIntrospector;
19
- private readonly _namingStrategy?;
19
+ private readonly _defaultForeignKeyAction?;
20
20
  private _mongoSchemaLoadPromise?;
21
21
  constructor(pool: QuerierPool, options?: MigratorOptions);
22
22
  /** Loads MongoDB schema generator on first use; SQL generators are set in the constructor (or via {@link setSchemaGenerator}). */
@@ -26,7 +26,7 @@ export declare class Migrator {
26
26
  */
27
27
  setSchemaGenerator(generator: SchemaGenerator): void;
28
28
  protected createIntrospector(): SchemaIntrospector | undefined;
29
- protected createGenerator(namingStrategy?: NamingStrategy): SchemaGenerator | undefined;
29
+ protected createGenerator(): SchemaGenerator | undefined;
30
30
  /**
31
31
  * Get all discovered migrations from the migrations directory
32
32
  */
@@ -88,6 +88,26 @@ export declare class Migrator {
88
88
  drop?: boolean;
89
89
  logging?: boolean;
90
90
  }): Promise<void>;
91
+ /**
92
+ * The DDL {@link autoSync} would run, without running it. Separate so `--dry-run` shows the real
93
+ * statements rather than a summary of a second, differently-computed diff.
94
+ */
95
+ planSync(options?: {
96
+ safe?: boolean;
97
+ drop?: boolean;
98
+ }): Promise<string[]>;
99
+ /**
100
+ * Each pending diff with the entity it came from, since resolving that is async and every caller
101
+ * needs it. What to emit stays with the caller: a sync narrows the forward direction to what the
102
+ * caller allows and never asks for the rollback, which on SQLite cannot even be expressed (no
103
+ * `ALTER COLUMN`), so computing it eagerly for everyone would throw there.
104
+ */
105
+ private pendingDiffs;
106
+ /**
107
+ * The schema generator. A getter because MongoDB's loads lazily (see {@link ensureSchemaGenerator}),
108
+ * so every caller had to repeat the same assertion after awaiting it.
109
+ */
110
+ private get generator();
91
111
  protected filterDiff(diff: SchemaDiff, options: {
92
112
  safe?: boolean;
93
113
  drop?: boolean;
@@ -7,6 +7,7 @@ import { isKnownMigratorDialect, isSqlQuerier } from '../type/index.js';
7
7
  import { LoggerWrapper } from '../util/index.js';
8
8
  import { acquireQuerierForMigrations } from './acquireQuerierForMigrations.js';
9
9
  import { buildSqlQuerierMigrationModule, EMPTY_MANUAL_MIGRATION_DOWN_INNER, EMPTY_MANUAL_MIGRATION_UP_INNER, emitSqlRunCalls, } from './codegen/migrationFile.js';
10
+ import { runMongoCommand } from './generator/mongoCommand.js';
10
11
  import { MongoSchemaIntrospector, MysqlSchemaIntrospector, PostgresSchemaIntrospector, SqliteSchemaIntrospector, } from './introspection/index.js';
11
12
  import { createSchemaGenerator } from './schemaGenerator.js';
12
13
  import { createSchemaGeneratorAsync } from './schemaGeneratorAsync.js';
@@ -32,12 +33,12 @@ export class Migrator {
32
33
  dialectName;
33
34
  schemaGenerator;
34
35
  schemaIntrospector;
35
- _namingStrategy;
36
+ _defaultForeignKeyAction;
36
37
  _mongoSchemaLoadPromise;
37
38
  constructor(pool, options = {}) {
38
39
  this.pool = pool;
39
40
  this.dialectName = pool.dialect.dialectName ?? 'postgres';
40
- this._namingStrategy = options.namingStrategy;
41
+ this._defaultForeignKeyAction = options.defaultForeignKeyAction;
41
42
  this.storage =
42
43
  options.storage ??
43
44
  new DatabaseMigrationStorage(pool, {
@@ -48,8 +49,7 @@ export class Migrator {
48
49
  this._entities = options.entities;
49
50
  this.schemaIntrospector = this.createIntrospector();
50
51
  this.schemaGenerator =
51
- options.schemaGenerator ??
52
- (this.dialectName === 'mongodb' ? undefined : this.createGenerator(options.namingStrategy));
52
+ options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
53
53
  }
54
54
  /** Loads MongoDB schema generator on first use; SQL generators are set in the constructor (or via {@link setSchemaGenerator}). */
55
55
  async ensureSchemaGenerator() {
@@ -57,7 +57,7 @@ export class Migrator {
57
57
  return;
58
58
  }
59
59
  if (!this._mongoSchemaLoadPromise) {
60
- this._mongoSchemaLoadPromise = createSchemaGeneratorAsync(this.pool.dialect, this._namingStrategy).then((gen) => {
60
+ this._mongoSchemaLoadPromise = createSchemaGeneratorAsync(this.pool.dialect, this._defaultForeignKeyAction).then((gen) => {
61
61
  if (gen) {
62
62
  this.schemaGenerator = gen;
63
63
  }
@@ -91,11 +91,11 @@ export class Migrator {
91
91
  return undefined;
92
92
  }
93
93
  }
94
- createGenerator(namingStrategy) {
94
+ createGenerator() {
95
95
  if (!isKnownMigratorDialect(this.dialectName)) {
96
96
  return undefined;
97
97
  }
98
- return createSchemaGenerator(this.pool.dialect, namingStrategy);
98
+ return createSchemaGenerator(this.pool.dialect, this._defaultForeignKeyAction);
99
99
  }
100
100
  /**
101
101
  * Get all discovered migrations from the migrations directory
@@ -251,26 +251,18 @@ export class Migrator {
251
251
  * Generate a migration based on entity schema differences
252
252
  */
253
253
  async generateFromEntities(name) {
254
- await this.ensureSchemaGenerator();
255
- if (!this.schemaGenerator) {
256
- throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
257
- }
258
- const diffs = await this.getDiffs();
259
254
  const upStatements = [];
260
255
  const downStatements = [];
261
- for (const diff of diffs) {
256
+ for (const { diff, entity } of await this.pendingDiffs()) {
262
257
  if (diff.type === 'create') {
263
- const entity = await this.findEntityForTable(diff.tableName);
264
258
  if (entity) {
265
- upStatements.push(...this.schemaGenerator.generateCreateTable(entity));
266
- downStatements.push(this.schemaGenerator.generateDropTable(entity));
259
+ upStatements.push(...this.generator.generateCreateTable(entity));
260
+ downStatements.push(this.generator.generateDropTable(diff.tableName, { ifExists: true }));
267
261
  }
268
262
  }
269
263
  else if (diff.type === 'alter') {
270
- const alterStatements = this.schemaGenerator.generateAlterTable(diff);
271
- upStatements.push(...alterStatements);
272
- const alterDownStatements = this.schemaGenerator.generateAlterTableDown(diff);
273
- downStatements.push(...alterDownStatements);
264
+ upStatements.push(...this.generator.generateAlterTable(diff));
265
+ downStatements.push(...this.generator.generateAlterTableDown(diff));
274
266
  }
275
267
  }
276
268
  if (upStatements.length === 0) {
@@ -342,9 +334,6 @@ export class Migrator {
342
334
  */
343
335
  async syncForce() {
344
336
  await this.ensureSchemaGenerator();
345
- if (!this.schemaGenerator) {
346
- throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
347
- }
348
337
  const querier = await acquireQuerierForMigrations(this.pool);
349
338
  if (!isSqlQuerier(querier)) {
350
339
  await querier.release();
@@ -354,13 +343,14 @@ export class Migrator {
354
343
  await querier.beginTransaction();
355
344
  // Drop all tables first (in reverse order for foreign keys)
356
345
  for (const entity of [...this.entities].reverse()) {
357
- const dropSql = this.schemaGenerator.generateDropTable(entity);
346
+ const tableName = this.generator.resolveTableName(entity, getMeta(entity));
347
+ const dropSql = this.generator.generateDropTable(tableName, { ifExists: true });
358
348
  this.logger.logSchema(`Executing: ${dropSql}`);
359
349
  await querier.run(dropSql);
360
350
  }
361
351
  // Create all tables
362
352
  for (const entity of this.entities) {
363
- const createStmts = this.schemaGenerator.generateCreateTable(entity);
353
+ const createStmts = this.generator.generateCreateTable(entity);
364
354
  for (const createSql of createStmts) {
365
355
  this.logger.logSchema(`Executing: ${createSql}`);
366
356
  await querier.run(createSql);
@@ -381,31 +371,53 @@ export class Migrator {
381
371
  * Safely synchronizes the schema by only adding missing tables and columns.
382
372
  */
383
373
  async autoSync(options = {}) {
384
- await this.ensureSchemaGenerator();
385
- if (!this.schemaGenerator || !this.schemaIntrospector) {
386
- throw new Error('Schema generator and introspector must be set');
374
+ const statements = await this.planSync(options);
375
+ if (statements.length === 0) {
376
+ if (options.logging)
377
+ this.logger.logSchema('Schema is already in sync.');
378
+ return;
387
379
  }
388
- const diffs = await this.getDiffs();
380
+ await this.executeSyncStatements(statements, options);
381
+ }
382
+ /**
383
+ * The DDL {@link autoSync} would run, without running it. Separate so `--dry-run` shows the real
384
+ * statements rather than a summary of a second, differently-computed diff.
385
+ */
386
+ async planSync(options = {}) {
389
387
  const statements = [];
390
- for (const diff of diffs) {
388
+ for (const { diff, entity } of await this.pendingDiffs()) {
391
389
  if (diff.type === 'create') {
392
- const entity = await this.findEntityForTable(diff.tableName);
393
- if (entity) {
394
- statements.push(...this.schemaGenerator.generateCreateTable(entity));
395
- }
390
+ if (entity)
391
+ statements.push(...this.generator.generateCreateTable(entity));
396
392
  }
397
393
  else if (diff.type === 'alter') {
398
- const filteredDiff = this.filterDiff(diff, options);
399
- const alterStatements = this.schemaGenerator.generateAlterTable(filteredDiff);
400
- statements.push(...alterStatements);
394
+ statements.push(...this.generator.generateAlterTable(this.filterDiff(diff, options)));
401
395
  }
402
396
  }
403
- if (statements.length === 0) {
404
- if (options.logging)
405
- this.logger.logSchema('Schema is already in sync.');
406
- return;
397
+ return statements;
398
+ }
399
+ /**
400
+ * Each pending diff with the entity it came from, since resolving that is async and every caller
401
+ * needs it. What to emit stays with the caller: a sync narrows the forward direction to what the
402
+ * caller allows and never asks for the rollback, which on SQLite cannot even be expressed (no
403
+ * `ALTER COLUMN`), so computing it eagerly for everyone would throw there.
404
+ */
405
+ async pendingDiffs() {
406
+ const diffs = await this.getDiffs();
407
+ return Promise.all(diffs.map(async (diff) => ({
408
+ diff,
409
+ entity: diff.type === 'create' ? await this.findEntityForTable(diff.tableName) : undefined,
410
+ })));
411
+ }
412
+ /**
413
+ * The schema generator. A getter because MongoDB's loads lazily (see {@link ensureSchemaGenerator}),
414
+ * so every caller had to repeat the same assertion after awaiting it.
415
+ */
416
+ get generator() {
417
+ if (!this.schemaGenerator) {
418
+ throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
407
419
  }
408
- await this.executeSyncStatements(statements, options);
420
+ return this.schemaGenerator;
409
421
  }
410
422
  filterDiff(diff, options) {
411
423
  const filteredDiff = { ...diff };
@@ -452,34 +464,10 @@ export class Migrator {
452
464
  }
453
465
  }
454
466
  async executeMongoSyncStatements(statements, options, querier) {
455
- const db = querier.db;
456
- for (const stmt of statements) {
457
- const cmd = JSON.parse(stmt);
467
+ for (const statement of statements) {
458
468
  if (options.logging)
459
- this.logger.logSchema(`Executing MongoDB: ${stmt}`);
460
- const collectionName = cmd.name || cmd.collection;
461
- if (!collectionName) {
462
- throw new Error(`MongoDB command missing collection name: ${stmt}`);
463
- }
464
- const collection = db.collection(collectionName);
465
- if (cmd.action === 'createCollection') {
466
- await db.createCollection(cmd.name);
467
- if (cmd.indexes?.length) {
468
- for (const idx of cmd.indexes) {
469
- const key = Object.fromEntries(idx.columns.map((c) => [c, 1]));
470
- await collection.createIndex(key, { unique: idx.unique, name: idx.name });
471
- }
472
- }
473
- }
474
- else if (cmd.action === 'dropCollection') {
475
- await collection.drop();
476
- }
477
- else if (cmd.action === 'createIndex') {
478
- await collection.createIndex(cmd.key, cmd.options);
479
- }
480
- else if (cmd.action === 'dropIndex') {
481
- await collection.dropIndex(cmd.name);
482
- }
469
+ this.logger.logSchema(`Executing MongoDB: ${statement}`);
470
+ await runMongoCommand(querier.db, statement);
483
471
  }
484
472
  }
485
473
  async executeSqlSyncStatements(statements, options, querier) {
@@ -1,12 +1,12 @@
1
1
  import { type AbstractDialect, AbstractSqlDialect } from '../dialect/index.js';
2
2
  import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
3
- import type { ColumnSchema, DialectFeatures, EntityMeta, FieldOptions, IndexSchema, NamingStrategy, SchemaDiff, SchemaGenerator, Type } from '../type/index.js';
3
+ import type { ColumnSchema, DialectFeatures, EntityMeta, FieldOptions, IndexSchema, NamingStrategy, SchemaDiff, SqlDdlGenerator, Type } from '../type/index.js';
4
4
  import type { FullColumnDefinition, TableDefinition, TableForeignKeyDefinition } from './builder/types.js';
5
5
  /**
6
6
  * Unified SQL schema generator.
7
7
  * Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
8
8
  */
9
- export declare class SqlSchemaGenerator implements SchemaGenerator {
9
+ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
10
10
  protected readonly dialect: AbstractSqlDialect;
11
11
  protected readonly defaultForeignKeyAction: ForeignKeyAction;
12
12
  constructor(dialect: AbstractSqlDialect, defaultForeignKeyAction?: ForeignKeyAction);
@@ -26,15 +26,14 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
26
26
  * Convert FieldOptions to CanonicalType using the unified type system.
27
27
  */
28
28
  protected getCanonicalType(field: FieldOptions, fieldType?: unknown): CanonicalType;
29
- /**
30
- * Convert CanonicalType to SQL type string for this dialect.
31
- * Also handles legacy string types for backward compatibility.
32
- */
33
- protected canonicalTypeToSql(type: CanonicalType | string): string;
29
+ protected canonicalTypeToSql(type: CanonicalType): string;
34
30
  generateCreateTable<E>(entity: Type<E>, options?: {
35
31
  ifNotExists?: boolean;
36
32
  }): string[];
37
- generateDropTable<E>(entity: Type<E>): string;
33
+ generateDropTable(tableName: string, options?: {
34
+ ifExists?: boolean;
35
+ cascade?: boolean;
36
+ }): string;
38
37
  generateAlterTable(diff: SchemaDiff): string[];
39
38
  generateAlterTableDown(diff: SchemaDiff): string[];
40
39
  generateCreateIndex(tableName: string, index: IndexSchema, options?: {
@@ -42,25 +41,24 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
42
41
  }): string;
43
42
  generateDropIndex(tableName: string, indexName: string): string;
44
43
  /**
45
- * Generate column definition from a ColumnSchema object
44
+ * Generate a column definition from a {@link ColumnSchema}, whose type is the engine's own spelling
45
+ * and may already carry its precision (or even `PRIMARY KEY`, for a serial).
46
46
  */
47
47
  generateColumnDefinitionFromSchema(column: ColumnSchema, options?: {
48
48
  includePrimaryKey?: boolean;
49
49
  includeUnique?: boolean;
50
50
  }): string;
51
- getSqlType(field: FieldOptions, fieldType?: unknown): string;
52
51
  /**
53
- * Get the boolean type for this database
52
+ * The one place a column definition is spelled. Both callers reach it - the `ColumnSchema` path above
53
+ * and the `ColumnNode` path below - because the clause order and the "no NOT NULL on a primary key"
54
+ * rules are the same everywhere, and having them written twice is how the two paths drifted.
54
55
  */
55
- getBooleanType(): string;
56
+ private renderColumn;
57
+ getSqlType(field: FieldOptions, fieldType?: unknown): string;
56
58
  /**
57
59
  * Generate ALTER COLUMN statements (database-specific)
58
60
  */
59
61
  generateAlterColumnStatements(tableName: string, column: ColumnSchema, newDefinition: string): string[];
60
- /**
61
- * Get table options (e.g., ENGINE for MySQL)
62
- */
63
- getTableOptions<E>(_meta: EntityMeta<E>): string;
64
62
  /**
65
63
  * Generate column comment clause (if supported)
66
64
  */
@@ -75,7 +73,8 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
75
73
  diffSchema<E>(entity: Type<E>, currentTable: TableNode | undefined): SchemaDiff | undefined;
76
74
  private columnNodeToSchema;
77
75
  /**
78
- * Convert field options to ColumnSchema
76
+ * Convert field options to ColumnSchema. Both sides of a diff are the engine's SQL spelling: what it
77
+ * would create for this field, against what it reported for the existing column.
79
78
  */
80
79
  protected fieldToColumnSchema<E>(fieldKey: string, field: FieldOptions, meta: EntityMeta<E>): ColumnSchema;
81
80
  /**
@@ -83,7 +82,13 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
83
82
  */
84
83
  protected columnsNeedAlteration(current: ColumnSchema, desired: ColumnSchema): boolean;
85
84
  /**
86
- * Compare two column types for equality using the canonical type system
85
+ * Whether two column types are the same *as this engine stores them*.
86
+ *
87
+ * Both sides are SQL spellings, parsed back to canonical so that `INT` and `INTEGER`, or `DATETIME`
88
+ * and `TIMESTAMP`, do not read as a change. Do not "simplify" this into comparing the entity's
89
+ * canonical type against the column's: several canonical types share one storage type per engine
90
+ * (`boolean` is `TINYINT(1)` on MySQL and `INTEGER` on SQLite), so that comparison reports an
91
+ * alteration for those columns on every single sync.
87
92
  */
88
93
  protected isTypeEqual(current: ColumnSchema, desired: ColumnSchema): boolean;
89
94
  /**
@@ -94,14 +99,10 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
94
99
  ifNotExists?: boolean;
95
100
  }): string[];
96
101
  /**
97
- * Generate a column definition from a ColumnNode.
102
+ * Generate a column definition from a ColumnNode. A composite key is declared as a table constraint,
103
+ * so only a lone primary key column carries `PRIMARY KEY` inline.
98
104
  */
99
105
  protected generateColumnFromNode(col: ColumnNode): string;
100
- /**
101
- * Generate an inline VECTOR INDEX clause for use inside CREATE TABLE.
102
- * Syntax: `VECTOR INDEX (col) M=n DISTANCE=metric`
103
- */
104
- private generateInlineVectorIndex;
105
106
  /**
106
107
  * Generate CREATE INDEX SQL from an IndexNode.
107
108
  * Delegates to `generateCreateIndex` for unified SQL assembly.
@@ -109,26 +110,14 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
109
110
  generateCreateIndexFromNode(index: IndexNode, options?: {
110
111
  ifNotExists: boolean;
111
112
  }): string;
112
- /**
113
- * Generate DROP TABLE SQL from a TableNode.
114
- */
115
- generateDropTableFromNode(table: TableNode, options?: {
116
- ifExists?: boolean;
117
- }): string;
118
113
  generateCreateTableFromDefinition(table: TableDefinition, options?: {
119
114
  ifNotExists?: boolean;
120
115
  }): string[];
121
- generateDropTableSql(tableName: string, options?: {
122
- ifExists?: boolean;
123
- cascade?: boolean;
124
- }): string;
125
116
  generateRenameTableSql(oldName: string, newName: string): string;
126
117
  generateAddColumnSql(tableName: string, column: FullColumnDefinition): string;
127
118
  generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string;
128
119
  generateDropColumnSql(tableName: string, columnName: string): string;
129
120
  generateRenameColumnSql(tableName: string, oldName: string, newName: string): string;
130
- generateCreateIndexSql(tableName: string, index: IndexSchema): string;
131
- generateDropIndexSql(tableName: string, indexName: string): string;
132
121
  generateAddForeignKeySql(tableName: string, foreignKey: TableForeignKeyDefinition): string;
133
122
  generateDropForeignKeySql(tableName: string, constraintName: string): string;
134
123
  private tableDefinitionToNode;
@@ -138,4 +127,4 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
138
127
  * Synchronous factory for SQL schema generators only.
139
128
  * For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
140
129
  */
141
- export declare function createSchemaGenerator(dialect: AbstractDialect, namingStrategy?: NamingStrategy, defaultForeignKeyAction?: ForeignKeyAction): SchemaGenerator | undefined;
130
+ export declare function createSchemaGenerator(dialect: AbstractDialect, defaultForeignKeyAction?: ForeignKeyAction): SqlSchemaGenerator | undefined;