uql-orm 0.63.0 → 0.64.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 (34) hide show
  1. package/dist/migrate/builder/expressions.js +1 -15
  2. package/dist/migrate/builder/migrationBuilder.d.ts +6 -28
  3. package/dist/migrate/builder/migrationBuilder.js +9 -83
  4. package/dist/migrate/builder/types.d.ts +14 -24
  5. package/dist/migrate/cli.d.ts +1 -6
  6. package/dist/migrate/cli.js +3 -10
  7. package/dist/migrate/codegen/migrationFile.d.ts +9 -1
  8. package/dist/migrate/codegen/migrationFile.js +2 -1
  9. package/dist/migrate/ddl/indexDdl.d.ts +4 -2
  10. package/dist/migrate/ddl/indexDdl.js +16 -15
  11. package/dist/migrate/generator/mongoCommand.d.ts +9 -9
  12. package/dist/migrate/generator/mongoCommand.js +1 -1
  13. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +21 -26
  14. package/dist/migrate/generator/mongoSchemaGenerator.js +103 -80
  15. package/dist/migrate/index.d.ts +1 -1
  16. package/dist/migrate/index.js +1 -1
  17. package/dist/migrate/indexPredicate.d.ts +8 -0
  18. package/dist/migrate/indexPredicate.js +52 -0
  19. package/dist/migrate/migrationTarget.d.ts +23 -0
  20. package/dist/migrate/migrationTarget.js +48 -0
  21. package/dist/migrate/migrator.d.ts +17 -45
  22. package/dist/migrate/migrator.js +80 -179
  23. package/dist/migrate/schemaGenerator.d.ts +12 -13
  24. package/dist/migrate/schemaGenerator.js +43 -9
  25. package/dist/mongo/mongoDialect.d.ts +6 -1
  26. package/dist/schema/schemaASTBuilder.d.ts +2 -0
  27. package/dist/schema/schemaASTBuilder.js +9 -22
  28. package/dist/type/migration.d.ts +11 -40
  29. package/dist/util/ddlExpression.util.d.ts +3 -1
  30. package/dist/util/ddlExpression.util.js +14 -0
  31. package/dist/util/sqlLiteral.js +5 -3
  32. package/package.json +1 -1
  33. package/dist/migrate/schemaGeneratorAsync.d.ts +0 -7
  34. package/dist/migrate/schemaGeneratorAsync.js +0 -12
@@ -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
  */
@@ -32,55 +26,35 @@ export class Migrator {
32
26
  return this._entities ?? getEntities();
33
27
  }
34
28
  dialectName;
29
+ /** The generator given, or this dialect's once {@link getSchemaGenerator} has loaded it. */
35
30
  schemaGenerator;
36
31
  schemaIntrospector;
37
- _defaultForeignKeyAction;
38
- _mongoSchemaLoadPromise;
32
+ defaultForeignKeyAction;
33
+ target;
39
34
  constructor(pool, options = {}) {
40
35
  this.pool = pool;
41
36
  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 }));
37
+ this.target = migrationTargetFor(pool.dialect);
38
+ this.defaultForeignKeyAction = options.defaultForeignKeyAction;
39
+ this.storage = options.storage ?? this.target.storage(pool, options.tableName);
48
40
  this.migrationsPath = options.migrationsPath ?? './migrations';
49
41
  this._logger = new LoggerWrapper(options.logger, { logValues: options.logValues, slowQuery: options.slowQuery });
50
42
  this._entities = options.entities;
51
43
  this.schemaIntrospector = this.createIntrospector();
52
- this.schemaGenerator =
53
- options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
44
+ this.schemaGenerator = options.schemaGenerator;
54
45
  }
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;
46
+ /** The schema generator, loaded on first use: MongoDB's needs its optional peer. */
47
+ async getSchemaGenerator() {
48
+ this.schemaGenerator ??= await this.target.generator(this.pool.dialect, this.defaultForeignKeyAction);
49
+ if (!this.schemaGenerator) {
50
+ throw new TypeError(`No schema generator for dialect '${this.dialectName}'`);
62
51
  }
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;
52
+ return this.schemaGenerator;
73
53
  }
74
54
  /** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
75
55
  createIntrospector(schema) {
76
56
  return introspectorFor(this.dialectName, this.pool, schema);
77
57
  }
78
- createGenerator() {
79
- if (!isKnownMigratorDialect(this.dialectName)) {
80
- return undefined;
81
- }
82
- return createSchemaGenerator(this.pool.dialect, this._defaultForeignKeyAction);
83
- }
84
58
  /**
85
59
  * Get all discovered migrations from the migrations directory
86
60
  */
@@ -159,10 +133,10 @@ export class Migrator {
159
133
  */
160
134
  async runMigration(migration, direction) {
161
135
  const startTime = Date.now();
162
- return this.withMigrationQuerier(async (querier, inTransaction) => {
136
+ return this.target.withSession(this.pool, async ({ querier, transaction }) => {
163
137
  try {
164
138
  this.logger.logMigration(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
165
- await inTransaction(async () => {
139
+ await transaction(async () => {
166
140
  if (direction === 'up') {
167
141
  await migration.up(querier);
168
142
  await this.storage.logWithQuerier(querier, migration.name);
@@ -194,25 +168,11 @@ export class Migrator {
194
168
  }
195
169
  });
196
170
  }
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
171
  /**
212
172
  * Generate a new migration file
213
173
  */
214
174
  async generate(name) {
215
- const { emptyUp, emptyDown } = migrationSource[this.migrationQuerier];
175
+ const { emptyUp, emptyDown } = this.target.source;
216
176
  const filePath = await this.writeMigration(name, { upInner: emptyUp, downInner: emptyDown });
217
177
  this.logger.logInfo(`Created migration: ${filePath}`);
218
178
  return filePath;
@@ -223,7 +183,7 @@ export class Migrator {
223
183
  const content = buildMigrationModule({
224
184
  migrationName: name,
225
185
  createdAt: new Date(),
226
- querier: this.migrationQuerier,
186
+ querier: this.target.source.querier,
227
187
  ...body,
228
188
  });
229
189
  await mkdir(this.migrationsPath, { recursive: true });
@@ -234,31 +194,25 @@ export class Migrator {
234
194
  * Generate a migration based on entity schema differences
235
195
  */
236
196
  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) {
197
+ const generator = await this.getSchemaGenerator();
198
+ const { created, altered } = await this.pendingChanges();
199
+ const up = [
200
+ ...this.createSchema(generator, created),
201
+ ...altered.flatMap((diff) => generator.generateAlterTable(diff)),
202
+ ];
203
+ if (up.length === 0) {
254
204
  this.logger.logInfo('No schema changes detected.');
255
205
  return '';
256
206
  }
257
- const { emit } = migrationSource[this.migrationQuerier];
207
+ const down = [
208
+ ...created.map((tableName) => generator.generateDropTable(tableName, { ifExists: true })),
209
+ ...altered.flatMap((diff) => generator.generateAlterTableDown(diff)),
210
+ ];
211
+ const { emit } = this.target.source;
258
212
  const filePath = await this.writeMigration(name, {
259
213
  docExtraLines: ['Generated from entity definitions'],
260
- upInner: emit(upStatements),
261
- downInner: emit([...downStatements].reverse()),
214
+ upInner: emit(up),
215
+ downInner: emit(down.reverse()),
262
216
  });
263
217
  this.logger.logInfo(`Created migration from entities: ${filePath}`);
264
218
  return filePath;
@@ -267,26 +221,20 @@ export class Migrator {
267
221
  * Get all schema differences between entities and database
268
222
  */
269
223
  async getDiffs() {
270
- await this.ensureSchemaGenerator();
271
- if (!this.schemaGenerator || !this.schemaIntrospector) {
272
- throw new TypeError('Schema generator and introspector must be set');
224
+ const generator = await this.getSchemaGenerator();
225
+ if (!this.schemaIntrospector) {
226
+ throw new TypeError(`No introspector for dialect '${this.dialectName}'`);
273
227
  }
274
228
  const ast = await this.introspectClaimedSchemas();
275
229
  // Both sides built once: the database's above, the entities' here. Left to `diffSchema`, each
276
230
  // entity would rebuild the whole AST, which is quadratic in the number of entities. Absent on a
277
231
  // 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;
232
+ const desiredAst = generator.buildAST?.(this.entities);
233
+ return this.entities.flatMap((entity) => {
234
+ const table = ast.getTable(generator.resolveTableName(getMeta(entity)));
235
+ const diff = generator.diffSchema(entity, table, desiredAst);
236
+ return diff ? [diff] : [];
237
+ });
290
238
  }
291
239
  /**
292
240
  * One AST spanning every schema the entities claim, each table stamped with the schema it was read
@@ -309,17 +257,6 @@ export class Migrator {
309
257
  }
310
258
  return merged;
311
259
  }
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
260
  /**
324
261
  * Applies the entity schema to the database: every registered entity, or the one `entity` names.
325
262
  *
@@ -344,10 +281,10 @@ export class Migrator {
344
281
  * with no referential integrity; and the old drop loop went in reverse *declaration* order, which
345
282
  * says nothing about the relation graph and is rejected as soon as the constraints are really there.
346
283
  */
347
- forceStatements() {
284
+ forceStatements(generator) {
348
285
  return [
349
- ...this.generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
350
- ...this.generator.generateCreateSchema(this.entities),
286
+ ...generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
287
+ ...generator.generateCreateSchema(this.entities),
351
288
  ];
352
289
  }
353
290
  /**
@@ -355,32 +292,26 @@ export class Migrator {
355
292
  * existence check and is created with `IF NOT EXISTS`, so instances racing the same admin save
356
293
  * settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
357
294
  */
358
- async planEntity(entity, options) {
295
+ async planEntity(generator, entity, options) {
359
296
  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
297
  const introspector = this.introspectorFor(this.pool.dialect.resolveSchema(meta));
363
298
  if (!introspector) {
364
299
  throw new TypeError(`No introspector for '${meta.entity.name}' on '${this.dialectName}'`);
365
300
  }
366
- const tableName = this.generator.resolveTableName(meta);
301
+ const tableName = generator.resolveTableName(meta);
367
302
  return (await introspector.tableExists(tableName))
368
- ? this.alterFromEntity(entity, (await introspectSchema(introspector)).getTable(tableName), options)
303
+ ? this.alterFromEntity(generator, entity, (await introspectSchema(introspector)).getTable(tableName), options)
369
304
  : // Spanning the whole set, so a foreign key resolves against the tables it points at, and
370
305
  // always including this entity: pinned to an explicit `entities` list, a sync of one outside
371
306
  // 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));
307
+ generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
377
308
  }
378
309
  /** The same for one entity against the table it already has, and nothing where the two agree. */
379
- alterFromEntity(entity, table, options) {
310
+ alterFromEntity(generator, entity, table, options) {
380
311
  // Spanning the set for the reason `planEntity` spells out: a foreign key needs the table it
381
312
  // 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) : [];
313
+ const diff = generator.diffSchema(entity, table, generator.buildAST?.(this.entitiesWith(entity)));
314
+ return diff?.type === 'alter' ? generator.generateAlterTable(this.filterDiff(diff, options)) : [];
384
315
  }
385
316
  /** The configured entities, with `entity` among them however the migrator was built. */
386
317
  entitiesWith(entity) {
@@ -396,25 +327,18 @@ export class Migrator {
396
327
  * statements rather than a summary of a second, differently-computed diff.
397
328
  */
398
329
  async planSync(options = {}) {
399
- await this.ensureSchemaGenerator();
330
+ const generator = await this.getSchemaGenerator();
400
331
  if (options.force) {
401
- return this.forceStatements();
332
+ return this.forceStatements(generator);
402
333
  }
403
334
  if (options.entity) {
404
- return this.planEntity(options.entity, options);
335
+ return this.planEntity(generator, options.entity, options);
405
336
  }
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
- }
416
- }
417
- return [...this.createSchema(creating), ...altering];
337
+ const { created, altered } = await this.pendingChanges();
338
+ return [
339
+ ...this.createSchema(generator, created),
340
+ ...altered.flatMap((diff) => generator.generateAlterTable(this.filterDiff(diff, options))),
341
+ ];
418
342
  }
419
343
  /**
420
344
  * New tables are emitted together, never one at a time: a single-entity AST has no other table for a
@@ -424,31 +348,20 @@ export class Migrator {
424
348
  *
425
349
  * Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
426
350
  */
427
- createSchema(tableNames) {
428
- return tableNames.length ? this.generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
351
+ createSchema(generator, tableNames) {
352
+ return tableNames.length ? generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
429
353
  }
430
354
  /**
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.
355
+ * The pending diffs a sync or a generated migration acts on: the tables to create and the tables to
356
+ * alter. What to emit for each stays with the caller: a sync narrows an alter to what it allows and
357
+ * never asks for the rollback, which on SQLite cannot even be expressed (no `ALTER COLUMN`).
435
358
  */
436
- async pendingDiffs() {
359
+ async pendingChanges() {
437
360
  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;
361
+ return {
362
+ created: diffs.filter((diff) => diff.type === 'create').map((diff) => diff.tableName),
363
+ altered: diffs.filter((diff) => diff.type === 'alter'),
364
+ };
452
365
  }
453
366
  filterDiff(diff, options) {
454
367
  const filteredDiff = { ...diff };
@@ -484,30 +397,18 @@ export class Migrator {
484
397
  }
485
398
  return filteredDiff;
486
399
  }
400
+ /** Runs the statements a generator wrote, in one transaction where the engine takes DDL in one. */
487
401
  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)));
402
+ await this.target.withSession(this.pool, ({ run, transaction }) => transaction(async () => {
403
+ for (const statement of statements) {
404
+ if (options.logging)
405
+ this.logger.logSchema(`Executing: ${statement}`);
406
+ await run(statement);
407
+ }
408
+ }));
491
409
  if (options.logging)
492
410
  this.logger.logSchema('Schema synchronization completed');
493
411
  }
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
412
  /**
512
413
  * Get migration status
513
414
  */
@@ -625,7 +526,7 @@ export function defineMigration(migration) {
625
526
  export function defineBuilderMigration(migration) {
626
527
  return {
627
528
  ...migration,
628
- up: (querier) => migration.up(new MigrationBuilder(querier), querier),
629
- down: (querier) => migration.down(new MigrationBuilder(querier), querier),
529
+ up: async (querier) => migration.up(await migrationBuilderFor(querier), querier),
530
+ down: async (querier) => migration.down(await migrationBuilderFor(querier), querier),
630
531
  };
631
532
  }
@@ -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, MigratorDialect, 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,6 @@ 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
- */
224
+ export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl' | 'compileIndexPredicate'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
225
+ /** The SQL schema generator for `dialect`, `undefined` on MongoDB, whose generator needs its optional peer. */
227
226
  export declare function createSchemaGenerator(dialect: MigratorDialect, defaultForeignKeyAction?: ForeignKeyAction): SqlSchemaGenerator | undefined;
@@ -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,11 @@ 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
- */
769
+ /** The SQL schema generator for `dialect`, `undefined` on MongoDB, whose generator needs its optional peer. */
736
770
  export function createSchemaGenerator(dialect, defaultForeignKeyAction) {
737
771
  return dialect.dialectName === 'mongodb' ? undefined : new SqlSchemaGenerator(dialect, defaultForeignKeyAction);
738
772
  }
@@ -10,6 +10,11 @@ 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
+ /** 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
+ };
13
18
  /** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
14
19
  export declare const mongoDialectFeatures: DialectFeatures;
15
20
  export declare class MongoDialect extends AbstractDialect {
@@ -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
@@ -29,6 +29,8 @@ export interface BuildSchemaASTOptions {
29
29
  * predicate - which only a dialect can render. `buildEntityAST` supplies it from the generator.
30
30
  */
31
31
  compileDdl?: (sql: EntityWhereMeta<object>, entity: Type<object>) => string;
32
+ /** A partial index's predicate as the engine writes it, `compileDdl` where none is given. `buildEntityAST` supplies it. */
33
+ compileIndexPredicate?: (where: EntityWhereMeta<object>, entity: Type<object>, indexName: string) => string;
32
34
  }
33
35
  /**
34
36
  * Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).