uql-orm 0.63.0 → 0.65.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 (98) hide show
  1. package/dist/bunSql/bunSqlQuerier.d.ts +3 -10
  2. package/dist/bunSql/bunSqlQuerier.js +2 -16
  3. package/dist/bunSql/bunSqlQuerierPool.d.ts +0 -2
  4. package/dist/bunSql/bunSqlQuerierPool.js +14 -16
  5. package/dist/d1/d1Querier.d.ts +13 -34
  6. package/dist/d1/d1Querier.js +1 -1
  7. package/dist/d1/d1QuerierPool.d.ts +3 -3
  8. package/dist/dialect/abstractDialect.d.ts +4 -9
  9. package/dist/dialect/abstractDialect.js +4 -5
  10. package/dist/dialect/abstractSqlDialect.d.ts +2 -2
  11. package/dist/dialect/index.d.ts +0 -1
  12. package/dist/dialect/index.js +2 -3
  13. package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
  14. package/dist/dialect/pgLikeSqlDialect.d.ts +9 -1
  15. package/dist/dialect/pgLikeSqlDialect.js +8 -5
  16. package/dist/dialect/queryContext.d.ts +3 -3
  17. package/dist/entity/metadata/definition.d.ts +6 -0
  18. package/dist/entity/metadata/definition.js +109 -76
  19. package/dist/migrate/builder/expressions.js +1 -15
  20. package/dist/migrate/builder/migrationBuilder.d.ts +6 -28
  21. package/dist/migrate/builder/migrationBuilder.js +9 -83
  22. package/dist/migrate/builder/types.d.ts +14 -24
  23. package/dist/migrate/cli.d.ts +2 -7
  24. package/dist/migrate/cli.js +22 -43
  25. package/dist/migrate/codegen/entityCodeGenerator.d.ts +5 -0
  26. package/dist/migrate/codegen/entityCodeGenerator.js +29 -7
  27. package/dist/migrate/codegen/indexDecoratorSource.js +1 -5
  28. package/dist/migrate/codegen/migrationFile.d.ts +9 -1
  29. package/dist/migrate/codegen/migrationFile.js +2 -1
  30. package/dist/migrate/codegen/sourceLiteral.d.ts +2 -0
  31. package/dist/migrate/codegen/sourceLiteral.js +4 -0
  32. package/dist/migrate/ddl/indexDdl.d.ts +4 -2
  33. package/dist/migrate/ddl/indexDdl.js +16 -15
  34. package/dist/migrate/generator/mongoCommand.d.ts +9 -9
  35. package/dist/migrate/generator/mongoCommand.js +1 -1
  36. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +21 -26
  37. package/dist/migrate/generator/mongoSchemaGenerator.js +103 -80
  38. package/dist/migrate/index.d.ts +2 -2
  39. package/dist/migrate/index.js +2 -2
  40. package/dist/migrate/indexPredicate.d.ts +8 -0
  41. package/dist/migrate/indexPredicate.js +52 -0
  42. package/dist/migrate/introspection/registry.d.ts +2 -2
  43. package/dist/migrate/introspection/registry.js +6 -11
  44. package/dist/migrate/migrationTarget.d.ts +23 -0
  45. package/dist/migrate/migrationTarget.js +50 -0
  46. package/dist/migrate/migrator.d.ts +18 -50
  47. package/dist/migrate/migrator.js +79 -199
  48. package/dist/migrate/schemaGenerator.d.ts +11 -14
  49. package/dist/migrate/schemaGenerator.js +42 -12
  50. package/dist/mongo/mongoDialect.d.ts +7 -2
  51. package/dist/mongo/mongoDialect.js +1 -4
  52. package/dist/mssql/mssqlDialect.js +0 -3
  53. package/dist/pglite/pgliteQuerier.d.ts +2 -6
  54. package/dist/pglite/pgliteQuerier.js +2 -9
  55. package/dist/postgres/index.d.ts +0 -1
  56. package/dist/postgres/index.js +0 -1
  57. package/dist/querier/abstractSharedHandleQuerierPool.d.ts +2 -5
  58. package/dist/querier/abstractSharedHandleQuerierPool.js +2 -5
  59. package/dist/querier/abstractSqlQuerier.d.ts +2 -3
  60. package/dist/querier/abstractSqlQuerier.js +8 -4
  61. package/dist/querier/cursorStream.d.ts +13 -0
  62. package/dist/{postgres/pgCursorStream.js → querier/cursorStream.js} +4 -13
  63. package/dist/schema/canonicalType.js +1 -1
  64. package/dist/schema/schemaASTBuilder.d.ts +2 -0
  65. package/dist/schema/schemaASTBuilder.js +43 -62
  66. package/dist/sqlite/abstractSqliteQuerier.d.ts +7 -3
  67. package/dist/sqlite/abstractSqliteQuerier.js +18 -4
  68. package/dist/sqlite/hranaQuerier.d.ts +1 -1
  69. package/dist/sqlite/localSqliteQuerierPool.d.ts +7 -0
  70. package/dist/sqlite/localSqliteQuerierPool.js +19 -0
  71. package/dist/sqlite/nodeSqliteQuerierPool.js +2 -3
  72. package/dist/sqlite/sqliteDialect.js +0 -3
  73. package/dist/sqlite/sqliteQuerier.d.ts +8 -5
  74. package/dist/sqlite/sqliteQuerier.js +4 -13
  75. package/dist/sqlite/sqliteQuerierPool.js +4 -4
  76. package/dist/turso/tursoSessionQuerier.d.ts +2 -2
  77. package/dist/turso/tursoSessionQuerier.js +16 -18
  78. package/dist/type/dialect.d.ts +25 -35
  79. package/dist/type/entity.d.ts +26 -28
  80. package/dist/type/migration.d.ts +11 -40
  81. package/dist/type/migratorDialect.d.ts +0 -9
  82. package/dist/type/migratorDialect.js +1 -16
  83. package/dist/type/querierPool.d.ts +0 -4
  84. package/dist/type/queryRaw.d.ts +2 -2
  85. package/dist/type/universalQuerier.d.ts +2 -2
  86. package/dist/util/ddlExpression.util.d.ts +3 -1
  87. package/dist/util/ddlExpression.util.js +14 -0
  88. package/dist/util/sqlLiteral.js +5 -3
  89. package/package.json +1 -3
  90. package/dist/migrate/schemaGeneratorAsync.d.ts +0 -7
  91. package/dist/migrate/schemaGeneratorAsync.js +0 -12
  92. package/dist/postgres/pgCursorStream.d.ts +0 -20
  93. package/dist/postgres/postgresWireDriverCapabilities.d.ts +0 -21
  94. package/dist/postgres/postgresWireDriverCapabilities.js +0 -21
  95. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +0 -27
  96. package/dist/sqlite/bunSqliteAdapter.bun.js +0 -25
  97. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -33
  98. package/dist/sqlite/nodeSqliteAdapter.js +0 -25
@@ -3,16 +3,10 @@ import { basename, extname, join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { getEntities, getMeta } from '../entity/index.js';
5
5
  import { introspectSchema, SchemaAST } from '../schema/index.js';
6
- import { isKnownMigratorDialect, isMongoQuerier, isSqlQuerier } from '../type/index.js';
7
6
  import { LoggerWrapper } from '../util/index.js';
8
- import { withMongoQuerierForMigrations, withSqlQuerierForMigrations } from './acquireQuerierForMigrations.js';
9
- import { MigrationBuilder } from './builder/migrationBuilder.js';
10
- import { buildMigrationModule, migrationSource, } from './codegen/migrationFile.js';
11
- import { runMongoCommand } from './generator/mongoCommand.js';
7
+ import { buildMigrationModule } from './codegen/migrationFile.js';
12
8
  import { introspectorFor } from './introspection/registry.js';
13
- import { createSchemaGenerator } from './schemaGenerator.js';
14
- import { DatabaseMigrationStorage } from './storage/databaseStorage.js';
15
- import { MongoMigrationStorage } from './storage/mongoStorage.js';
9
+ import { migrationBuilderFor, migrationTargetFor } from './migrationTarget.js';
16
10
  /**
17
11
  * Main class for managing database migrations
18
12
  */
@@ -31,55 +25,24 @@ export class Migrator {
31
25
  get entities() {
32
26
  return this._entities ?? getEntities();
33
27
  }
34
- dialectName;
28
+ /** The generator given, or this dialect's once {@link getSchemaGenerator} has loaded it. */
35
29
  schemaGenerator;
36
30
  schemaIntrospector;
37
- _defaultForeignKeyAction;
38
- _mongoSchemaLoadPromise;
31
+ target;
39
32
  constructor(pool, options = {}) {
40
33
  this.pool = pool;
41
- this.dialectName = pool.dialect.dialectName;
42
- this._defaultForeignKeyAction = options.defaultForeignKeyAction;
43
- this.storage =
44
- options.storage ??
45
- (this.dialectName === 'mongodb'
46
- ? new MongoMigrationStorage(pool, { tableName: options.tableName })
47
- : new DatabaseMigrationStorage(pool, { tableName: options.tableName }));
34
+ this.target = migrationTargetFor(pool, options.defaultForeignKeyAction);
35
+ this.storage = options.storage ?? this.target.storage(options.tableName);
48
36
  this.migrationsPath = options.migrationsPath ?? './migrations';
49
37
  this._logger = new LoggerWrapper(options.logger, { logValues: options.logValues, slowQuery: options.slowQuery });
50
38
  this._entities = options.entities;
51
- this.schemaIntrospector = this.createIntrospector();
52
- this.schemaGenerator =
53
- options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
39
+ this.schemaIntrospector = introspectorFor(pool);
40
+ this.schemaGenerator = options.schemaGenerator;
54
41
  }
55
- /**
56
- * Loads MongoDB's schema generator on first use, so the optional `mongodb` peer loads only then. SQL
57
- * generators are set in the constructor (or via {@link setSchemaGenerator}).
58
- */
59
- async ensureSchemaGenerator() {
60
- if (this.schemaGenerator || this.dialectName !== 'mongodb') {
61
- return;
62
- }
63
- this._mongoSchemaLoadPromise ??= import('./generator/mongoSchemaGenerator.js').then(({ MongoSchemaGenerator }) => {
64
- this.schemaGenerator = new MongoSchemaGenerator(this.pool.dialect.namingStrategy, this._defaultForeignKeyAction);
65
- });
66
- await this._mongoSchemaLoadPromise;
67
- }
68
- /**
69
- * Set the schema generator for DDL operations
70
- */
71
- setSchemaGenerator(generator) {
72
- this.schemaGenerator = generator;
73
- }
74
- /** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
75
- createIntrospector(schema) {
76
- return introspectorFor(this.dialectName, this.pool, schema);
77
- }
78
- createGenerator() {
79
- if (!isKnownMigratorDialect(this.dialectName)) {
80
- return undefined;
81
- }
82
- return createSchemaGenerator(this.pool.dialect, this._defaultForeignKeyAction);
42
+ /** The schema generator, loaded on first use: MongoDB's needs its optional peer. */
43
+ async getSchemaGenerator() {
44
+ this.schemaGenerator ??= await this.target.generator();
45
+ return this.schemaGenerator;
83
46
  }
84
47
  /**
85
48
  * Get all discovered migrations from the migrations directory
@@ -159,10 +122,10 @@ export class Migrator {
159
122
  */
160
123
  async runMigration(migration, direction) {
161
124
  const startTime = Date.now();
162
- return this.withMigrationQuerier(async (querier, inTransaction) => {
125
+ return this.target.withSession(async ({ querier, transaction }) => {
163
126
  try {
164
127
  this.logger.logMigration(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
165
- await inTransaction(async () => {
128
+ await transaction(async () => {
166
129
  if (direction === 'up') {
167
130
  await migration.up(querier);
168
131
  await this.storage.logWithQuerier(querier, migration.name);
@@ -194,25 +157,11 @@ export class Migrator {
194
157
  }
195
158
  });
196
159
  }
197
- /**
198
- * A migration querier, and how to run work in one transaction on it. MongoDB gets none: it creates
199
- * collections and indexes outside any transaction. SQL asserts its querier before opening one, so a
200
- * wrong querier reports which one the dialect needs rather than a missing `transaction`.
201
- */
202
- withMigrationQuerier(task) {
203
- return this.dialectName === 'mongodb'
204
- ? withMongoQuerierForMigrations(this.pool, 'Migrator', (querier) => task(querier, (work) => work()))
205
- : withSqlQuerierForMigrations(this.pool, 'Migrator', (querier) => task(querier, (work) => querier.transaction(work)));
206
- }
207
- /** What this dialect's migration files are written against. */
208
- get migrationQuerier() {
209
- return this.dialectName === 'mongodb' ? 'MongoQuerier' : 'SqlQuerier';
210
- }
211
160
  /**
212
161
  * Generate a new migration file
213
162
  */
214
163
  async generate(name) {
215
- const { emptyUp, emptyDown } = migrationSource[this.migrationQuerier];
164
+ const { emptyUp, emptyDown } = this.target.source;
216
165
  const filePath = await this.writeMigration(name, { upInner: emptyUp, downInner: emptyDown });
217
166
  this.logger.logInfo(`Created migration: ${filePath}`);
218
167
  return filePath;
@@ -223,7 +172,7 @@ export class Migrator {
223
172
  const content = buildMigrationModule({
224
173
  migrationName: name,
225
174
  createdAt: new Date(),
226
- querier: this.migrationQuerier,
175
+ querier: this.target.source.querier,
227
176
  ...body,
228
177
  });
229
178
  await mkdir(this.migrationsPath, { recursive: true });
@@ -234,31 +183,25 @@ export class Migrator {
234
183
  * Generate a migration based on entity schema differences
235
184
  */
236
185
  async generateFromEntities(name) {
237
- const creating = [];
238
- const altering = [];
239
- const downStatements = [];
240
- for (const { diff, entity } of await this.pendingDiffs()) {
241
- if (diff.type === 'create') {
242
- if (entity) {
243
- creating.push(diff.tableName);
244
- downStatements.push(this.generator.generateDropTable(diff.tableName, { ifExists: true }));
245
- }
246
- }
247
- else if (diff.type === 'alter') {
248
- altering.push(...this.generator.generateAlterTable(diff));
249
- downStatements.push(...this.generator.generateAlterTableDown(diff));
250
- }
251
- }
252
- const upStatements = [...this.createSchema(creating), ...altering];
253
- if (upStatements.length === 0) {
186
+ const generator = await this.getSchemaGenerator();
187
+ const { created, altered } = await this.pendingChanges();
188
+ const up = [
189
+ ...this.createSchema(generator, created),
190
+ ...altered.flatMap((diff) => generator.generateAlterTable(diff)),
191
+ ];
192
+ if (up.length === 0) {
254
193
  this.logger.logInfo('No schema changes detected.');
255
194
  return '';
256
195
  }
257
- const { emit } = migrationSource[this.migrationQuerier];
196
+ const down = [
197
+ ...created.map((tableName) => generator.generateDropTable(tableName, { ifExists: true })),
198
+ ...altered.flatMap((diff) => generator.generateAlterTableDown(diff)),
199
+ ];
200
+ const { emit } = this.target.source;
258
201
  const filePath = await this.writeMigration(name, {
259
202
  docExtraLines: ['Generated from entity definitions'],
260
- upInner: emit(upStatements),
261
- downInner: emit([...downStatements].reverse()),
203
+ upInner: emit(up),
204
+ downInner: emit(down.reverse()),
262
205
  });
263
206
  this.logger.logInfo(`Created migration from entities: ${filePath}`);
264
207
  return filePath;
@@ -267,26 +210,17 @@ export class Migrator {
267
210
  * Get all schema differences between entities and database
268
211
  */
269
212
  async getDiffs() {
270
- await this.ensureSchemaGenerator();
271
- if (!this.schemaGenerator || !this.schemaIntrospector) {
272
- throw new TypeError('Schema generator and introspector must be set');
273
- }
213
+ const generator = await this.getSchemaGenerator();
274
214
  const ast = await this.introspectClaimedSchemas();
275
215
  // Both sides built once: the database's above, the entities' here. Left to `diffSchema`, each
276
216
  // entity would rebuild the whole AST, which is quadratic in the number of entities. Absent on a
277
217
  // generator that compares no schema of its own - MongoDB, which reads only indexes.
278
- const desiredAst = this.schemaGenerator.buildAST?.(this.entities);
279
- const diffs = [];
280
- for (const entity of this.entities) {
281
- const meta = getMeta(entity);
282
- const tableName = this.schemaGenerator.resolveTableName(meta);
283
- const currentTable = ast.getTable(tableName);
284
- const diff = this.schemaGenerator.diffSchema(entity, currentTable, desiredAst);
285
- if (diff) {
286
- diffs.push(diff);
287
- }
288
- }
289
- return diffs;
218
+ const desiredAst = generator.buildAST?.(this.entities);
219
+ return this.entities.flatMap((entity) => {
220
+ const table = ast.getTable(generator.resolveTableName(getMeta(entity)));
221
+ const diff = generator.diffSchema(entity, table, desiredAst);
222
+ return diff ? [diff] : [];
223
+ });
290
224
  }
291
225
  /**
292
226
  * One AST spanning every schema the entities claim, each table stamped with the schema it was read
@@ -299,27 +233,12 @@ export class Migrator {
299
233
  const claimed = new Set(this.entities.map((entity) => this.pool.dialect.resolveSchema(getMeta(entity))));
300
234
  const merged = new SchemaAST();
301
235
  for (const schema of claimed) {
302
- const introspector = this.introspectorFor(schema);
303
- if (!introspector) {
304
- continue;
305
- }
306
- for (const table of (await introspectSchema(introspector)).getTables()) {
236
+ for (const table of (await introspectSchema(this.schemaIntrospectorFor(schema))).getTables()) {
307
237
  merged.addTable(table);
308
238
  }
309
239
  }
310
240
  return merged;
311
241
  }
312
- async findEntityForTable(tableName) {
313
- await this.ensureSchemaGenerator();
314
- for (const entity of this.entities) {
315
- const meta = getMeta(entity);
316
- const name = this.generator.resolveTableName(meta);
317
- if (name === tableName) {
318
- return entity;
319
- }
320
- }
321
- return undefined;
322
- }
323
242
  /**
324
243
  * Applies the entity schema to the database: every registered entity, or the one `entity` names.
325
244
  *
@@ -344,10 +263,10 @@ export class Migrator {
344
263
  * with no referential integrity; and the old drop loop went in reverse *declaration* order, which
345
264
  * says nothing about the relation graph and is rejected as soon as the constraints are really there.
346
265
  */
347
- forceStatements() {
266
+ forceStatements(generator) {
348
267
  return [
349
- ...this.generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
350
- ...this.generator.generateCreateSchema(this.entities),
268
+ ...generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
269
+ ...generator.generateCreateSchema(this.entities),
351
270
  ];
352
271
  }
353
272
  /**
@@ -355,32 +274,23 @@ export class Migrator {
355
274
  * existence check and is created with `IF NOT EXISTS`, so instances racing the same admin save
356
275
  * settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
357
276
  */
358
- async planEntity(entity, options) {
277
+ async planEntity(generator, entity, options) {
359
278
  const meta = getMeta(entity);
360
- // Before anything reads `this.generator`, whose own failure names neither the entity nor the
361
- // dialect that has no support.
362
- const introspector = this.introspectorFor(this.pool.dialect.resolveSchema(meta));
363
- if (!introspector) {
364
- throw new TypeError(`No introspector for '${meta.entity.name}' on '${this.dialectName}'`);
365
- }
366
- const tableName = this.generator.resolveTableName(meta);
279
+ const introspector = this.schemaIntrospectorFor(this.pool.dialect.resolveSchema(meta));
280
+ const tableName = generator.resolveTableName(meta);
367
281
  return (await introspector.tableExists(tableName))
368
- ? this.alterFromEntity(entity, (await introspectSchema(introspector)).getTable(tableName), options)
282
+ ? this.alterFromEntity(generator, entity, (await introspectSchema(introspector)).getTable(tableName), options)
369
283
  : // Spanning the whole set, so a foreign key resolves against the tables it points at, and
370
284
  // always including this entity: pinned to an explicit `entities` list, a sync of one outside
371
285
  // it emitted nothing at all. `only` is what keeps the statements to this table.
372
- this.generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
373
- }
374
- /** An alter diff as statements, narrowed to what the caller allows. */
375
- alterFromDiff(diff, options) {
376
- return this.generator.generateAlterTable(this.filterDiff(diff, options));
286
+ generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
377
287
  }
378
288
  /** The same for one entity against the table it already has, and nothing where the two agree. */
379
- alterFromEntity(entity, table, options) {
289
+ alterFromEntity(generator, entity, table, options) {
380
290
  // Spanning the set for the reason `planEntity` spells out: a foreign key needs the table it
381
291
  // points at, which a sync of one entity outside the configured list would not otherwise have.
382
- const diff = this.generator.diffSchema(entity, table, this.generator.buildAST?.(this.entitiesWith(entity)));
383
- return diff?.type === 'alter' ? this.alterFromDiff(diff, options) : [];
292
+ const diff = generator.diffSchema(entity, table, generator.buildAST?.(this.entitiesWith(entity)));
293
+ return diff?.type === 'alter' ? generator.generateAlterTable(this.filterDiff(diff, options)) : [];
384
294
  }
385
295
  /** The configured entities, with `entity` among them however the migrator was built. */
386
296
  entitiesWith(entity) {
@@ -388,33 +298,26 @@ export class Migrator {
388
298
  return entities.includes(entity) ? entities : [...entities, entity];
389
299
  }
390
300
  /** The introspector for a claimed schema, which is the connection's own where none is claimed. */
391
- introspectorFor(schema) {
392
- return schema === undefined ? this.schemaIntrospector : this.createIntrospector(schema);
301
+ schemaIntrospectorFor(schema) {
302
+ return schema === undefined ? this.schemaIntrospector : introspectorFor(this.pool, schema);
393
303
  }
394
304
  /**
395
305
  * The DDL {@link sync} would run, without running it. Separate so `--dry-run` shows the real
396
306
  * statements rather than a summary of a second, differently-computed diff.
397
307
  */
398
308
  async planSync(options = {}) {
399
- await this.ensureSchemaGenerator();
309
+ const generator = await this.getSchemaGenerator();
400
310
  if (options.force) {
401
- return this.forceStatements();
311
+ return this.forceStatements(generator);
402
312
  }
403
313
  if (options.entity) {
404
- return this.planEntity(options.entity, options);
405
- }
406
- const creating = [];
407
- const altering = [];
408
- for (const { diff, entity } of await this.pendingDiffs()) {
409
- if (diff.type === 'create') {
410
- if (entity)
411
- creating.push(diff.tableName);
412
- }
413
- else if (diff.type === 'alter') {
414
- altering.push(...this.alterFromDiff(diff, options));
415
- }
314
+ return this.planEntity(generator, options.entity, options);
416
315
  }
417
- return [...this.createSchema(creating), ...altering];
316
+ const { created, altered } = await this.pendingChanges();
317
+ return [
318
+ ...this.createSchema(generator, created),
319
+ ...altered.flatMap((diff) => generator.generateAlterTable(this.filterDiff(diff, options))),
320
+ ];
418
321
  }
419
322
  /**
420
323
  * New tables are emitted together, never one at a time: a single-entity AST has no other table for a
@@ -424,31 +327,20 @@ export class Migrator {
424
327
  *
425
328
  * Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
426
329
  */
427
- createSchema(tableNames) {
428
- return tableNames.length ? this.generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
330
+ createSchema(generator, tableNames) {
331
+ return tableNames.length ? generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
429
332
  }
430
333
  /**
431
- * Each pending diff with the entity it came from, since resolving that is async and every caller
432
- * needs it. What to emit stays with the caller: a sync narrows the forward direction to what the
433
- * caller allows and never asks for the rollback, which on SQLite cannot even be expressed (no
434
- * `ALTER COLUMN`), so computing it eagerly for everyone would throw there.
334
+ * The pending diffs a sync or a generated migration acts on: the tables to create and the tables to
335
+ * alter. What to emit for each stays with the caller: a sync narrows an alter to what it allows and
336
+ * never asks for the rollback, which on SQLite cannot even be expressed (no `ALTER COLUMN`).
435
337
  */
436
- async pendingDiffs() {
338
+ async pendingChanges() {
437
339
  const diffs = await this.getDiffs();
438
- return Promise.all(diffs.map(async (diff) => ({
439
- diff,
440
- entity: diff.type === 'create' ? await this.findEntityForTable(diff.tableName) : undefined,
441
- })));
442
- }
443
- /**
444
- * The schema generator. A getter because MongoDB's loads lazily (see {@link ensureSchemaGenerator}),
445
- * so every caller had to repeat the same assertion after awaiting it.
446
- */
447
- get generator() {
448
- if (!this.schemaGenerator) {
449
- throw new TypeError('Schema generator not set. Call setSchemaGenerator() first.');
450
- }
451
- return this.schemaGenerator;
340
+ return {
341
+ created: diffs.filter((diff) => diff.type === 'create').map((diff) => diff.tableName),
342
+ altered: diffs.filter((diff) => diff.type === 'alter'),
343
+ };
452
344
  }
453
345
  filterDiff(diff, options) {
454
346
  const filteredDiff = { ...diff };
@@ -484,30 +376,18 @@ export class Migrator {
484
376
  }
485
377
  return filteredDiff;
486
378
  }
379
+ /** Runs the statements a generator wrote, in one transaction where the engine takes DDL in one. */
487
380
  async executeSyncStatements(statements, options) {
488
- await this.withMigrationQuerier((querier, inTransaction) => inTransaction(() => isMongoQuerier(querier)
489
- ? this.executeMongoSyncStatements(statements, options, querier)
490
- : this.executeSqlSyncStatements(statements, options, querier)));
381
+ await this.target.withSession(({ run, transaction }) => transaction(async () => {
382
+ for (const statement of statements) {
383
+ if (options.logging)
384
+ this.logger.logSchema(`Executing: ${statement}`);
385
+ await run(statement);
386
+ }
387
+ }));
491
388
  if (options.logging)
492
389
  this.logger.logSchema('Schema synchronization completed');
493
390
  }
494
- async executeMongoSyncStatements(statements, options, querier) {
495
- for (const statement of statements) {
496
- if (options.logging)
497
- this.logger.logSchema(`Executing MongoDB: ${statement}`);
498
- await runMongoCommand(querier.db, statement);
499
- }
500
- }
501
- async executeSqlSyncStatements(statements, options, querier) {
502
- if (!isSqlQuerier(querier)) {
503
- throw new TypeError('Migrator requires a SQL-based querier for this dialect');
504
- }
505
- for (const sql of statements) {
506
- if (options.logging)
507
- this.logger.logSchema(`Executing: ${sql}`);
508
- await querier.run(sql);
509
- }
510
- }
511
391
  /**
512
392
  * Get migration status
513
393
  */
@@ -625,7 +505,7 @@ export function defineMigration(migration) {
625
505
  export function defineBuilderMigration(migration) {
626
506
  return {
627
507
  ...migration,
628
- up: (querier) => migration.up(new MigrationBuilder(querier), querier),
629
- down: (querier) => migration.down(new MigrationBuilder(querier), querier),
508
+ up: async (querier) => migration.up(await migrationBuilderFor(querier), querier),
509
+ down: async (querier) => migration.down(await migrationBuilderFor(querier), querier),
630
510
  };
631
511
  }
@@ -2,14 +2,14 @@ import type { AbstractSqlDialect } from '../dialect/index.js';
2
2
  import type { SchemaAST } from '../schema/schemaAST.js';
3
3
  import { type DiffOptions } from '../schema/schemaASTDiffer.js';
4
4
  import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
5
- import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, MigratorDialect, NamingStrategy, SchemaDiff, SchemaGenerator, SqlDdlGenerator, Type } from '../type/index.js';
6
- import type { FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
5
+ import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, NamingStrategy, SchemaDiff, SchemaGenerator, Type } from '../type/index.js';
6
+ import type { AnyMigrationOperation, FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
7
7
  import { type IndexDdl, type TableDdl } from './ddl/index.js';
8
8
  /**
9
9
  * Unified SQL schema generator.
10
10
  * Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
11
11
  */
12
- export declare class SqlSchemaGenerator implements SqlDdlGenerator {
12
+ export declare class SqlSchemaGenerator implements SchemaGenerator {
13
13
  protected readonly dialect: AbstractSqlDialect;
14
14
  protected readonly defaultForeignKeyAction: ForeignKeyAction;
15
15
  /** `CREATE INDEX` for this dialect: the migrator's, so a runtime import carries none of it. */
@@ -24,6 +24,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
24
24
  resolveSchema<E>(meta: EntityMeta<E>): string | undefined;
25
25
  resolveColumnName(key: string, field: FieldOptions): string;
26
26
  compileDdl(sql: EntityWhereMeta<object>, entity: Type<object>): string;
27
+ compileIndexPredicate(where: EntityWhereMeta<object>, entity: Type<object>, indexName: string): string;
27
28
  /** Escape an identifier (table name, column name, etc.) */
28
29
  protected escapeId(identifier: string): string;
29
30
  /**
@@ -168,17 +169,18 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
168
169
  }): string[];
169
170
  generateCreateIndexFromDefinition(tableName: string, index: IndexDefinition): string;
170
171
  generateRenameTableSql(oldName: string, newName: string): string;
172
+ /** `raw` is split, being the one SQL no generator wrote. */
173
+ generateOperation(operation: AnyMigrationOperation): string[];
171
174
  /**
172
175
  * `ADD COLUMN`, plus the constraint and index the column declares.
173
176
  *
174
177
  * `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
175
178
  * no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
176
- * dropped both without a word. Several statements in one string is what `generateAlterColumnSql`
177
- * already returns, and `execute` splits them.
179
+ * dropped both without a word.
178
180
  */
179
- generateAddColumnSql(tableName: string, column: FullColumnDefinition): string;
180
- generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string;
181
- generateDropColumnSql(tableName: string, columnName: string): string;
181
+ generateAddColumnSql(tableName: string, column: FullColumnDefinition): string[];
182
+ generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string[];
183
+ generateDropColumnSql(tableName: string, columnName: string): string[];
182
184
  generateRenameColumnSql(tableName: string, oldName: string, newName: string): string;
183
185
  /**
184
186
  * `CONSTRAINT <name> FOREIGN KEY (...) REFERENCES ... ON DELETE ... ON UPDATE ...`.
@@ -219,9 +221,4 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
219
221
  * other way and the table is created under one name and compared under another, which reports every
220
222
  * table of a project using a naming strategy as both missing and unexpected.
221
223
  */
222
- export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
223
- /**
224
- * Synchronous factory for SQL schema generators only.
225
- * For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
226
- */
227
- export declare function createSchemaGenerator(dialect: MigratorDialect, defaultForeignKeyAction?: ForeignKeyAction): SqlSchemaGenerator | undefined;
224
+ export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl' | 'compileIndexPredicate'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
@@ -6,10 +6,12 @@ import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
6
6
  import { isAutoIncrement, qualifyName } from '../util/index.js';
7
7
  import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '../util/sql.util.js';
8
8
  import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
9
+ import { splitSqlStatements } from './builder/splitSqlStatements.js';
9
10
  import { indexDdlFor, tableDdlFor } from './ddl/index.js';
10
11
  import { sizedType } from './ddl/tableDdl.js';
11
12
  import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, renderIndexDefinition, tableDefinitionToNode, } from './generator/definitionToNode.js';
12
13
  import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
14
+ import { assertIndexPredicate } from './indexPredicate.js';
13
15
  /**
14
16
  * Unified SQL schema generator.
15
17
  * Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
@@ -48,6 +50,10 @@ export class SqlSchemaGenerator {
48
50
  compileDdl(sql, entity) {
49
51
  return this.dialect.compileDdl(sql, entity);
50
52
  }
53
+ compileIndexPredicate(where, entity, indexName) {
54
+ assertIndexPredicate(where, this.dialect.dialectName, indexName);
55
+ return this.dialect.compileDdl(where, entity);
56
+ }
51
57
  /** Escape an identifier (table name, column name, etc.) */
52
58
  escapeId(identifier) {
53
59
  return this.dialect.escapeId(identifier);
@@ -577,13 +583,43 @@ export class SqlSchemaGenerator {
577
583
  generateRenameTableSql(oldName, newName) {
578
584
  return this.tableDdl.renameTable(oldName, newName);
579
585
  }
586
+ /** `raw` is split, being the one SQL no generator wrote. */
587
+ generateOperation(operation) {
588
+ switch (operation.type) {
589
+ case 'createTable':
590
+ return this.generateCreateTableFromDefinition(operation.table);
591
+ case 'dropTable':
592
+ return [
593
+ this.generateDropTable(operation.tableName, { ifExists: operation.ifExists, cascade: operation.cascade }),
594
+ ];
595
+ case 'renameTable':
596
+ return [this.generateRenameTableSql(operation.oldName, operation.newName)];
597
+ case 'addColumn':
598
+ return this.generateAddColumnSql(operation.tableName, operation.column);
599
+ case 'dropColumn':
600
+ return this.generateDropColumnSql(operation.tableName, operation.columnName);
601
+ case 'renameColumn':
602
+ return [this.generateRenameColumnSql(operation.tableName, operation.oldName, operation.newName)];
603
+ case 'alterColumn':
604
+ return this.generateAlterColumnSql(operation.tableName, operation.columnName, operation.changes);
605
+ case 'createIndex':
606
+ return [this.generateCreateIndexFromDefinition(operation.tableName, operation.index)];
607
+ case 'dropIndex':
608
+ return [this.generateDropIndex(operation.tableName, operation.indexName)];
609
+ case 'addForeignKey':
610
+ return [this.generateAddForeignKeySql(operation.tableName, operation.foreignKey)];
611
+ case 'dropForeignKey':
612
+ return [this.generateDropForeignKeySql(operation.tableName, operation.constraintName)];
613
+ case 'raw':
614
+ return splitSqlStatements(operation.sql);
615
+ }
616
+ }
580
617
  /**
581
618
  * `ADD COLUMN`, plus the constraint and index the column declares.
582
619
  *
583
620
  * `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
584
621
  * no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
585
- * dropped both without a word. Several statements in one string is what `generateAlterColumnSql`
586
- * already returns, and `execute` splits them.
622
+ * dropped both without a word.
587
623
  */
588
624
  generateAddColumnSql(tableName, column) {
589
625
  this.assertColumnAddable(tableName, column);
@@ -598,14 +634,14 @@ export class SqlSchemaGenerator {
598
634
  statements.push(this.generateCreateIndex(tableName, index));
599
635
  }
600
636
  statements.push(...this.generateColumnCommentStatement(tableName, column));
601
- return statements.join('\n');
637
+ return statements;
602
638
  }
603
639
  generateAlterColumnSql(tableName, columnName, column) {
604
640
  const node = fullColumnDefinitionToNode(column, tableName);
605
- return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node)).join('\n');
641
+ return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node));
606
642
  }
607
643
  generateDropColumnSql(tableName, columnName) {
608
- return this.tableDdl.dropColumn(tableName, columnName).join('\n');
644
+ return this.tableDdl.dropColumn(tableName, columnName);
609
645
  }
610
646
  generateRenameColumnSql(tableName, oldName, newName) {
611
647
  return this.tableDdl.renameColumn(tableName, oldName, newName);
@@ -726,13 +762,7 @@ export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
726
762
  resolveSchema: (meta) => generator.resolveSchema(meta),
727
763
  resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
728
764
  compileDdl: (sql, entity) => generator.compileDdl(sql, entity),
765
+ compileIndexPredicate: (where, entity, indexName) => generator.compileIndexPredicate(where, entity, indexName),
729
766
  defaultForeignKeyAction,
730
767
  });
731
768
  }
732
- /**
733
- * Synchronous factory for SQL schema generators only.
734
- * For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
735
- */
736
- export function createSchemaGenerator(dialect, defaultForeignKeyAction) {
737
- return dialect.dialectName === 'mongodb' ? undefined : new SqlSchemaGenerator(dialect, defaultForeignKeyAction);
738
- }
@@ -10,7 +10,12 @@ type MongoReadStages = {
10
10
  /** Keys merged into the query's projection, when it has one: a vector search's score. */
11
11
  readonly project?: Record<string, 1>;
12
12
  };
13
- /** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
13
+ /** Accumulator threaded through `$where` rendering: the relation lookups it needs, and their temp fields. */
14
+ type RelationLookups = {
15
+ readonly stages: MongoAggregationPipelineEntry<Document>[];
16
+ readonly temps: string[];
17
+ };
18
+ /** Default {@link DialectFeatures} for MongoDB. */
14
19
  export declare const mongoDialectFeatures: DialectFeatures;
15
20
  export declare class MongoDialect extends AbstractDialect {
16
21
  #private;
@@ -46,7 +51,7 @@ export declare class MongoDialect extends AbstractDialect {
46
51
  * recursion). Relation keys need `$lookup` stages, so they are only accepted when `lookups` is
47
52
  * given - a plain `find`/`updateMany` filter has nowhere to put them.
48
53
  */
49
- private renderFilter;
54
+ protected renderFilter<E extends Document>(entity: Type<E>, where?: QueryWhere<E>, lookups?: RelationLookups): Filter<E>;
50
55
  /**
51
56
  * Renders `$and`/`$or`/`$not`/`$nor` into `filter`. MongoDB has no root-level `$not`, so both
52
57
  * negating operators become its `$nor`, which is exactly `NOT (a OR b)` - and by De Morgan that
@@ -6,11 +6,8 @@ import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/
6
6
  import { COUNT_RESULT_KEY } from '../type/query.js';
7
7
  import { QueryRaw } from '../type/queryRaw.js';
8
8
  import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonUpdateOp, isOperatorMap, isOperatorObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
9
- /** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
9
+ /** Default {@link DialectFeatures} for MongoDB. */
10
10
  export const mongoDialectFeatures = {
11
- explicitJsonCast: false,
12
- nativeArrays: false,
13
- supportsJsonb: false,
14
11
  ifNotExists: false,
15
12
  indexIfNotExists: false,
16
13
  schemas: false, // the connection picks the database, and a collection name takes no dot
@@ -20,9 +20,6 @@ import { escapeSingleQuotes } from '../util/sqlLiteral.js';
20
20
  */
21
21
  export class MsSqlDialect extends MergeSqlDialect {
22
22
  featureDefaults = {
23
- explicitJsonCast: false,
24
- nativeArrays: false,
25
- supportsJsonb: false,
26
23
  // Neither object takes an `IF NOT EXISTS`; both need a `sys` catalogue lookup around them, which
27
24
  // the generator does not emit.
28
25
  ifNotExists: false,