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
@@ -1,6 +1,6 @@
1
1
  import { SOFT_DELETE_FILTER } from '../../type/index.js';
2
2
  import { isInlinedExpression } from '../../util/field.util.js';
3
- import { entityName, entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, lowerFirst, memberRefs, normalizeIndexColumn, upperFirst, definedEntries, } from '../../util/index.js';
3
+ import { entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, lowerFirst, memberRefs, normalizeIndexColumn, upperFirst, definedEntries, } from '../../util/index.js';
4
4
  import { ownRegistrations } from '../decorator/bag.js';
5
5
  /**
6
6
  * A map held on `globalThis` through the global symbol registry, so a single one survives multiple
@@ -54,10 +54,11 @@ export function defineRelation(entity, key, opts) {
54
54
  * exist, and the registry holds data alone.
55
55
  */
56
56
  export function relationRegistration({ mappedBy, references, ...opts }) {
57
+ const joined = references?.(keyMap(), keyMap());
57
58
  return {
58
59
  ...opts,
59
60
  ...(mappedBy && { mappedBy: mappedBy(keyMap()) }),
60
- ...(references && { references: [...references(keyMap(), keyMap())] }),
61
+ ...(joined && { references: typeof joined === 'string' ? joined : [...joined] }),
61
62
  };
62
63
  }
63
64
  /** Every entity's key map: a callback only reads one property off it, and that property is its own key. */
@@ -70,8 +71,8 @@ function addRelation(entity, key, registration) {
70
71
  throw new TypeError(`'${entity.name}.${key}' needs an 'entity' getter, e.g. '@ManyToOne({ entity: () => Company })'.`);
71
72
  }
72
73
  if (registration.through && registration.references) {
73
- throw new TypeError(`'${entity.name}.${key}' joins through a junction, whose columns follow the convention; 'references' ` +
74
- "pairs the declaring entity's columns with the target's instead.");
74
+ throw new TypeError(`'${entity.name}.${key}' joins through a junction, whose column referencing each side is the join; ` +
75
+ "'references' pairs the declaring entity's columns with the target's instead.");
75
76
  }
76
77
  const meta = ensureWritableMeta(entity);
77
78
  // Registration writes into a map declared as resolved: `getMeta` runs `fillRelations`, which settles
@@ -312,51 +313,62 @@ export function getMeta(entity) {
312
313
  if (meta.processedAt === meta.revision) {
313
314
  return meta;
314
315
  }
315
- // Stamped before finalizing, not after: `fillInverseSide` reads the other side through `getMeta`,
316
- // and with each side mapped by the other that recursion has to find this half-filled meta rather
317
- // than run again. Finalizing twice is harmless anyway - every step of it skips what it settled.
316
+ // Stamped before finalizing: `fillInverseSide` reads the other side through `getMeta`, and with each
317
+ // side mapped by the other that recursion has to find this half-filled meta rather than run again.
318
+ // Unstamped when finalizing throws, so the next read reports the same mistake; running it again is
319
+ // harmless, since every step skips what it settled.
318
320
  meta.processedAt = meta.revision;
319
- return fillRelations(meta);
321
+ try {
322
+ return fillRelations(meta);
323
+ }
324
+ catch (error) {
325
+ meta.processedAt = undefined;
326
+ throw error;
327
+ }
320
328
  }
321
329
  function fillRelations(meta) {
322
330
  for (const [relKey, relation] of definedEntries(meta.relations)) {
323
- // The registered view: `references` may be unset until this settles it.
331
+ // The registered view: `references` may be unset, or the one column a to-one names, until this settles it.
324
332
  const relOpts = relation;
325
333
  const at = `'${meta.entity.name}.${relKey}'`;
326
- if (relOpts.mappedBy) {
327
- fillInverseSide(at, meta, relOpts, relOpts.mappedBy);
328
- }
329
- else if (!relOpts.references) {
330
- fillOwningSide(at, meta, relKey, relOpts);
331
- }
332
- if (!relOpts.references?.length) {
334
+ const references = relOpts.mappedBy
335
+ ? fillInverseSide(at, meta, relOpts, relOpts.mappedBy)
336
+ : (pairedReferences(at, relOpts) ?? fillOwningSide(at, meta, relKey, relOpts));
337
+ if (!references.length) {
333
338
  throw new TypeError(`${at} has no columns to join on.`);
334
339
  }
335
- // Hand-written `references` land here too: naming the columns says which they are, not that they exist.
336
- if (relOpts.through) {
337
- const junction = getMeta(relOpts.through());
338
- for (const { local } of relOpts.references) {
339
- if (junction.fields[local])
340
- continue;
341
- throw new TypeError(`${at} joins through '${junction.entity.name}', which has no '${local}' field: a junction's ` +
342
- 'columns are named after the entities it joins. Declare it.');
343
- }
344
- }
345
340
  }
346
- fillForeignKeyRelations(meta);
341
+ // A column `references` names is a foreign key with or without a relation over it, and one cannot point
342
+ // at a composite key: refused on first read, as a relation that cannot join is, not at the schema build.
343
+ foreignKeysOf(meta);
347
344
  return meta;
348
345
  }
346
+ /**
347
+ * The pairs a relation joins on, with the one column a to-one names paired with the target's primary key:
348
+ * on first read rather than at registration, which can run before the target has a key to pair with.
349
+ */
350
+ function pairedReferences(at, relOpts) {
351
+ const { references } = relOpts;
352
+ if (typeof references !== 'string') {
353
+ return references;
354
+ }
355
+ const target = ensureMeta(relOpts.entity());
356
+ if (relOpts.mappedBy || isToManyRelation(relOpts) || target.ids.length > 1) {
357
+ throw new TypeError(`${at} names one column, '${references}', which only a to-one holding a foreign key to a one-column key ` +
358
+ 'can: pair the columns, [{ local, foreign }].');
359
+ }
360
+ relOpts.references = [{ local: references, foreign: soleIdOf(target, 'a foreign key') }];
361
+ return relOpts.references;
362
+ }
349
363
  function fillOwningSide(at, meta, relKey, relOpts) {
350
364
  const relMeta = ensureMeta(relOpts.entity());
351
365
  if (relOpts.through) {
352
366
  // Both columns live on the junction, whatever the cardinality: `deleteRelations` and every dialect
353
367
  // read them as junction columns. A composite key contributes one pair per column of it, which is
354
368
  // what makes the join address a whole key rather than part.
355
- relOpts.references = [
356
- ...meta.ids.map((key) => ({ local: junctionColumn(meta, key), foreign: key })),
357
- ...relMeta.ids.map((key) => ({ local: junctionColumn(relMeta, key), foreign: key })),
358
- ];
359
- return;
369
+ const junction = getMeta(relOpts.through());
370
+ relOpts.references = [...junctionReferences(at, junction, meta), ...junctionReferences(at, junction, relMeta)];
371
+ return relOpts.references;
360
372
  }
361
373
  if (isToManyRelation(relOpts)) {
362
374
  throw new TypeError(`${at} is a to-many relation with no way to join: it needs 'mappedBy' (the field on the other side), ` +
@@ -366,16 +378,23 @@ function fillOwningSide(at, meta, relKey, relOpts) {
366
378
  // property, so both are spelled from the referenced *property* - a column name is what the naming
367
379
  // strategy makes of this afterwards.
368
380
  const sole = relMeta.ids.length === 1;
369
- relOpts.references = relMeta.ids.map((key) => ({
381
+ const references = relMeta.ids.map((key) => ({
370
382
  local: sole ? `${relKey}Id` : `${relKey}${upperFirst(key)}`,
371
383
  foreign: key,
372
384
  }));
385
+ // A column the entity declares would be joined by its name alone, so renaming either one would leave
386
+ // the other behind, still compiling.
387
+ const fields = meta.fields;
388
+ if (references.some(({ local }) => fields[local])) {
389
+ const own = lowerFirst(meta.entity.name);
390
+ throw new TypeError(`${at} joins ${references.map(({ local }) => `'${local}'`).join(', ')} by name, which a rename does not ` +
391
+ `follow: link them with ${sole ? `'references: (${own}) => ${own}.${references[0].local}'` : "'references' pairs"}.`);
392
+ }
373
393
  // `typeFromReference` so schema generation resolves the referenced primary key's exact type
374
394
  // (columnType, length, chained keys) rather than trusting the fallback, as it does for an
375
395
  // explicit `@Field({ references })`.
376
- const fields = meta.fields;
377
- for (const { local, foreign } of relOpts.references) {
378
- fields[local] ??= {
396
+ for (const { local, foreign } of references) {
397
+ fields[local] = {
379
398
  name: local,
380
399
  type: fieldOf(relMeta, foreign).type ?? Number,
381
400
  references: relOpts.entity,
@@ -383,12 +402,15 @@ function fillOwningSide(at, meta, relKey, relOpts) {
383
402
  typeFromReference: true,
384
403
  };
385
404
  }
405
+ relOpts.references = references;
406
+ return references;
386
407
  }
387
408
  function fillInverseSide(at, meta, relOpts, mappedBy) {
388
409
  const relEntity = relOpts.entity();
389
410
  const relMeta = getMeta(relEntity);
390
- if (relOpts.references)
391
- return;
411
+ const own = pairedReferences(at, relOpts);
412
+ if (own)
413
+ return own;
392
414
  if (relMeta.fields[mappedBy]) {
393
415
  if (meta.ids.length > 1) {
394
416
  throw new TypeError(`${at} is mapped by '${relEntity.name}.${mappedBy}', one column, but the primary key of ` +
@@ -397,7 +419,7 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
397
419
  }
398
420
  // `local` is this entity's own key, as in every other pair.
399
421
  relOpts.references = [{ local: meta.ids[0], foreign: mappedBy }];
400
- return;
422
+ return relOpts.references;
401
423
  }
402
424
  // Authored view again: with each side mapped by the other, the target is still mid-resolution here and
403
425
  // its own `references` are unset, which is what the second throw reports.
@@ -405,7 +427,8 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
405
427
  if (!owner) {
406
428
  throw new TypeError(`${at} is mapped by '${mappedBy}', which is neither a field nor a relation of '${relEntity.name}'.`);
407
429
  }
408
- if (!owner.references?.length) {
430
+ const ownerReferences = pairedReferences(`'${relEntity.name}.${mappedBy}'`, owner);
431
+ if (!ownerReferences?.length) {
409
432
  throw new TypeError(`${at} is mapped by '${relEntity.name}.${mappedBy}', an inverse side too, so neither owns the foreign key.`);
410
433
  }
411
434
  // Two different flips: a junction's pairs are the owner's group followed by ours, so the two groups
@@ -413,50 +436,55 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
413
436
  // plain foreign key is one pair per key whose ends swap.
414
437
  relOpts.references =
415
438
  relOpts.cardinality === 'm1' || relOpts.cardinality === 'mm'
416
- ? [...owner.references.slice(relMeta.ids.length), ...owner.references.slice(0, relMeta.ids.length)]
417
- : owner.references.map(({ local, foreign }) => ({ local: foreign, foreign: local }));
439
+ ? [...ownerReferences.slice(relMeta.ids.length), ...ownerReferences.slice(0, relMeta.ids.length)]
440
+ : ownerReferences.map(({ local, foreign }) => ({ local: foreign, foreign: local }));
418
441
  relOpts.through = owner.through;
442
+ return relOpts.references;
419
443
  }
420
444
  /**
421
- * A field carrying `references` is a foreign key, and a foreign key is a many-to-one whether or not
422
- * anyone declared the relation. Deriving it everywhere is what lets a junction be written as two plain
423
- * columns: it needs the relations for `$populate` and for its DDL constraints, and it used to get them
424
- * only because some *other* entity pointed `through` at it. Gaps only, so a declared relation keeps its
425
- * own cardinality and `cascade`.
445
+ * The foreign keys an entity holds: each owning to-one's columns, and each `@Field({ references })` no
446
+ * relation joins on, as the many-to-one it describes, once its target has registered a key. What the
447
+ * schema build constrains and a junction joins by.
426
448
  */
427
- function fillForeignKeyRelations(meta) {
428
- const joined = new Set(definedEntries(meta.relations).flatMap(([, relation]) => relation.references.map(({ local }) => local)));
429
- for (const [fieldKey, { references }] of definedEntries(meta.fields)) {
430
- if (!references || joined.has(fieldKey))
431
- continue;
432
- const target = ensureMeta(references());
433
- // Nothing to derive from an entity that has not registered its own fields yet.
449
+ export function foreignKeysOf(meta) {
450
+ const owning = definedEntries(meta.relations)
451
+ .map(([, relation]) => relation)
452
+ .filter(({ cardinality, mappedBy }) => cardinality === 'm1' || (cardinality === '11' && !mappedBy));
453
+ const joined = new Set(owning.flatMap(({ references }) => references.map(({ local }) => local)));
454
+ const columns = definedEntries(meta.fields).flatMap(([key, field]) => {
455
+ if (!field.references || joined.has(key))
456
+ return [];
457
+ const target = ensureMeta(field.references());
434
458
  if (!target.ids.length)
435
- continue;
459
+ return [];
436
460
  if (target.ids.length > 1) {
437
- throw new TypeError(`'${meta.entity.name}.${fieldKey}' cannot reference '${target.entity.name}', whose primary key is composite ` +
461
+ throw new TypeError(`'${meta.entity.name}.${key}' cannot reference '${target.entity.name}', whose primary key is composite ` +
438
462
  `(${target.ids.join(', ')}): a column points at one. Use ` +
439
463
  `'@ManyToOne({ entity: () => ${target.entity.name} })', which declares one column per key.`);
440
464
  }
441
- const [foreign] = target.ids;
442
- // The relation takes the column's name minus the key it points at (`itemId` -> `item`); a column
443
- // named anything else has no name to take, so it stays a plain foreign key.
444
- const suffix = upperFirst(foreign);
445
- if (!fieldKey.endsWith(suffix))
446
- continue;
447
- const relKey = fieldKey.slice(0, -suffix.length);
448
- if (!relKey || meta.fields[relKey] || meta.relations[relKey])
449
- continue;
450
- meta.relations[relKey] = {
451
- entity: references,
452
- cardinality: 'm1',
453
- references: [{ local: fieldKey, foreign }],
454
- };
455
- }
456
- }
457
- /** `<entityName><IdColumn>`, not the `<relationKey>Id` an owning to-one derives: a junction row has no relation key to borrow from. */
458
- function junctionColumn(meta, idKey) {
459
- return lowerFirst(entityName(meta)) + upperFirst(fieldOf(meta, idKey).name ?? idKey);
465
+ return [{ entity: field.references, cardinality: 'm1', references: [{ local: key, foreign: target.ids[0] }] }];
466
+ });
467
+ return [...owning, ...columns];
468
+ }
469
+ /** Each key of `side`, paired with the one column of `junction` referencing it: a rename of either follows. */
470
+ function junctionReferences(at, junction, side) {
471
+ const pairs = foreignKeysOf(junction).flatMap(({ entity, references }) => entity() === side.entity ? references : []);
472
+ return side.ids.map((key) => {
473
+ const [pair, ...others] = pairs.filter(({ foreign }) => foreign === key);
474
+ const referenced = `'${side.entity.name}.${key}'`;
475
+ if (!pair) {
476
+ const declare = side.ids.length > 1
477
+ ? `@ManyToOne({ entity: () => ${side.entity.name} })`
478
+ : `@Field({ references: () => ${side.entity.name} })`;
479
+ throw new TypeError(`${at} joins through '${junction.entity.name}', which has no column referencing ${referenced}: declare one, '${declare}'.`);
480
+ }
481
+ if (others.length) {
482
+ const columns = [pair, ...others].map(({ local }) => `'${local}'`).join(' and ');
483
+ throw new TypeError(`${at} joins through '${junction.entity.name}', where ${columns} each reference ${referenced}: a junction ` +
484
+ 'needs exactly one column per key of each side.');
485
+ }
486
+ return { local: pair.local, foreign: key };
487
+ });
460
488
  }
461
489
  /** Every key the entity marks, in declaration order. More than one is a composite primary key. */
462
490
  function getIdKeys(meta) {
@@ -490,7 +518,12 @@ function extendMeta(target, source) {
490
518
  }
491
519
  }
492
520
  target.fields = { ...sourceFields, ...target.fields };
493
- target.relations = { ...source.relations, ...target.relations };
521
+ // A copy of each relation per entity: resolving one writes the columns it joins on into it, and those
522
+ // columns are the resolving entity's, so one shared object left every later entity without them.
523
+ target.relations = {
524
+ ...Object.fromEntries(definedEntries(source.relations).map(([key, relation]) => [key, { ...relation }])),
525
+ ...target.relations,
526
+ };
494
527
  // Inherit user-defined filters from the parent (child overrides by name). The built-in soft-delete
495
528
  // filter + `meta.softDelete` are (re)derived from the merged fields in `defineEntity`.
496
529
  if (source.filters) {
@@ -99,21 +99,7 @@ function defaultLiteral(value, dialect) {
99
99
  if (typeof value === 'boolean') {
100
100
  return dialect.booleanLiteral === 'native' ? (value ? 'TRUE' : 'FALSE') : value ? '1' : '0';
101
101
  }
102
- if (value instanceof Date) {
103
- return dialect.escape(ddlTimestamp(value));
104
- }
105
- if (typeof value === 'object') {
106
- return dialect.escape(JSON.stringify(value));
107
- }
108
- return dialect.escape(value);
109
- }
110
- /**
111
- * `YYYY-MM-DD HH:mm:ss.SSS` in UTC. Not `toISOString`, whose `T` and `Z` MySQL rejects outright
112
- * ("Invalid default value"), and not `escape`'s local-time form, which would make the DDL depend on
113
- * the machine that generated it.
114
- */
115
- function ddlTimestamp(date) {
116
- return date.toISOString().replace('T', ' ').replace('Z', '');
102
+ return dialect.escape(typeof value === 'object' && !(value instanceof Date) ? JSON.stringify(value) : value);
117
103
  }
118
104
  function expressionSql(expression, dialect) {
119
105
  const { expressions } = DIALECT_DEFAULTS[dialect.dialectName];
@@ -1,13 +1,6 @@
1
- /**
2
- * Migration Builder
3
- *
4
- * Provides type-safe migration operations with two modes:
5
- * - OperationRecorder: Record operations only (for code generation)
6
- * - MigrationBuilder: Execute DDL operations (for integration tests/runtime)
7
- */
8
1
  import type { ForeignKeyAction } from '../../schema/types.js';
9
2
  import type { IndexColumnInput, IndexOptions } from '../../type/index.js';
10
- import type { SqlQuerier } from '../../type/querier.js';
3
+ import type { SchemaGenerator } from '../../type/migration.js';
11
4
  import type { AnyMigrationOperation, IAlterTableBuilder, IColumnBuilder, IColumnFactory, IMigrationBuilder, ITableBuilder } from './types.js';
12
5
  type ForeignKeyTarget = {
13
6
  table: string;
@@ -49,28 +42,13 @@ export declare class OperationRecorder implements IMigrationBuilder {
49
42
  getOperations(): AnyMigrationOperation[];
50
43
  }
51
44
  /**
52
- * Executes DDL operations via a SQL querier.
53
- * Use for integration tests and runtime schema management.
54
- *
55
- * @example
56
- * ```typescript
57
- * const builder = new MigrationBuilder(querier);
58
- *
59
- * await builder.createTable('users', (t) => {
60
- * t.id();
61
- * t.string('name');
62
- * t.timestamps();
63
- * });
64
- * ```
45
+ * Records each operation, then runs the statements `generator` writes for it through `run`. Build one
46
+ * for a querier with `migrationBuilderFor`.
65
47
  */
66
48
  export declare class MigrationBuilder extends OperationRecorder {
67
- private readonly querier;
68
- private readonly sqlGenerator;
69
- constructor(querier: SqlQuerier);
70
- /** The recorder's sink, plus the statements the operation turns into. */
49
+ private readonly generator;
50
+ private readonly run;
51
+ constructor(generator: SchemaGenerator, run: (statement: string) => Promise<unknown>);
71
52
  protected record(operation: AnyMigrationOperation): Promise<void>;
72
- private getCreateTableStatements;
73
- private execute;
74
- private operationToSql;
75
53
  }
76
54
  export {};
@@ -1,14 +1,5 @@
1
- /**
2
- * Migration Builder
3
- *
4
- * Provides type-safe migration operations with two modes:
5
- * - OperationRecorder: Record operations only (for code generation)
6
- * - MigrationBuilder: Execute DDL operations (for integration tests/runtime)
7
- */
8
1
  import { indexNameParts, normalizeIndexColumn } from '../../util/index.js';
9
2
  import { derivedIndexName } from '../../util/sql.util.js';
10
- import { createSchemaGenerator } from '../schemaGenerator.js';
11
- import { splitSqlStatements } from './splitSqlStatements.js';
12
3
  import { TableBuilder } from './tableBuilder.js';
13
4
  /**
14
5
  * One `createIndex` operation. Shared because the alter-table builder, the recorder and the
@@ -239,86 +230,21 @@ export class OperationRecorder {
239
230
  }
240
231
  }
241
232
  /**
242
- * Executes DDL operations via a SQL querier.
243
- * Use for integration tests and runtime schema management.
244
- *
245
- * @example
246
- * ```typescript
247
- * const builder = new MigrationBuilder(querier);
248
- *
249
- * await builder.createTable('users', (t) => {
250
- * t.id();
251
- * t.string('name');
252
- * t.timestamps();
253
- * });
254
- * ```
233
+ * Records each operation, then runs the statements `generator` writes for it through `run`. Build one
234
+ * for a querier with `migrationBuilderFor`.
255
235
  */
256
236
  export class MigrationBuilder extends OperationRecorder {
257
- querier;
258
- sqlGenerator;
259
- constructor(querier) {
237
+ generator;
238
+ run;
239
+ constructor(generator, run) {
260
240
  super();
261
- this.querier = querier;
262
- const generator = createSchemaGenerator(querier.dialect);
263
- if (!generator) {
264
- throw new TypeError(`Could not find a schema generator for dialect: ${querier.dialect.dialectName}`);
265
- }
266
- this.sqlGenerator = generator;
241
+ this.generator = generator;
242
+ this.run = run;
267
243
  }
268
- /** The recorder's sink, plus the statements the operation turns into. */
269
244
  async record(operation) {
270
245
  await super.record(operation);
271
- await this.execute(operation);
272
- }
273
- getCreateTableStatements(operation) {
274
- return this.sqlGenerator.generateCreateTableFromDefinition(operation.table);
275
- }
276
- async execute(operation) {
277
- if (operation.type === 'createTable') {
278
- for (const statement of this.getCreateTableStatements(operation)) {
279
- await this.querier.run(statement);
280
- }
281
- return;
282
- }
283
- const sql = this.operationToSql(operation);
284
- if (sql) {
285
- for (const statement of splitSqlStatements(sql)) {
286
- await this.querier.run(statement);
287
- }
288
- }
289
- }
290
- operationToSql(operation) {
291
- switch (operation.type) {
292
- case 'createTable':
293
- // One line per statement in preview; execute() runs each separately (#87).
294
- return this.getCreateTableStatements(operation).join('\n');
295
- case 'dropTable':
296
- return this.sqlGenerator.generateDropTable(operation.tableName, {
297
- ifExists: operation.ifExists,
298
- cascade: operation.cascade,
299
- });
300
- case 'renameTable':
301
- return this.sqlGenerator.generateRenameTableSql(operation.oldName, operation.newName);
302
- case 'addColumn':
303
- return this.sqlGenerator.generateAddColumnSql(operation.tableName, operation.column);
304
- case 'dropColumn':
305
- return this.sqlGenerator.generateDropColumnSql(operation.tableName, operation.columnName);
306
- case 'renameColumn':
307
- return this.sqlGenerator.generateRenameColumnSql(operation.tableName, operation.oldName, operation.newName);
308
- case 'alterColumn':
309
- return this.sqlGenerator.generateAlterColumnSql(operation.tableName, operation.columnName, operation.changes);
310
- case 'createIndex':
311
- return this.sqlGenerator.generateCreateIndexFromDefinition(operation.tableName, operation.index);
312
- case 'dropIndex':
313
- return this.sqlGenerator.generateDropIndex(operation.tableName, operation.indexName);
314
- case 'addForeignKey':
315
- return this.sqlGenerator.generateAddForeignKeySql(operation.tableName, operation.foreignKey);
316
- case 'dropForeignKey':
317
- return this.sqlGenerator.generateDropForeignKeySql(operation.tableName, operation.constraintName);
318
- case 'raw':
319
- return operation.sql;
320
- default:
321
- return undefined;
246
+ for (const statement of this.generator.generateOperation(operation)) {
247
+ await this.run(statement);
322
248
  }
323
249
  }
324
250
  }
@@ -116,20 +116,10 @@ export interface TableDefinition {
116
116
  /** Table comment */
117
117
  comment?: string;
118
118
  }
119
- /**
120
- * Type of migration operation.
121
- */
122
- export type MigrationOperationType = 'createTable' | 'dropTable' | 'renameTable' | 'alterTable' | 'addColumn' | 'dropColumn' | 'alterColumn' | 'renameColumn' | 'createIndex' | 'dropIndex' | 'addForeignKey' | 'dropForeignKey' | 'raw';
123
- /**
124
- * Base migration operation.
125
- */
126
- export interface MigrationOperation {
127
- type: MigrationOperationType;
128
- }
129
119
  /**
130
120
  * Create table operation.
131
121
  */
132
- export interface CreateTableOperation extends MigrationOperation {
122
+ export interface CreateTableOperation {
133
123
  type: 'createTable';
134
124
  table: TableDefinition;
135
125
  ifNotExists?: boolean;
@@ -137,7 +127,7 @@ export interface CreateTableOperation extends MigrationOperation {
137
127
  /**
138
128
  * Drop table operation.
139
129
  */
140
- export interface DropTableOperation extends MigrationOperation {
130
+ export interface DropTableOperation {
141
131
  type: 'dropTable';
142
132
  tableName: string;
143
133
  ifExists?: boolean;
@@ -146,7 +136,7 @@ export interface DropTableOperation extends MigrationOperation {
146
136
  /**
147
137
  * Rename table operation.
148
138
  */
149
- export interface RenameTableOperation extends MigrationOperation {
139
+ export interface RenameTableOperation {
150
140
  type: 'renameTable';
151
141
  oldName: string;
152
142
  newName: string;
@@ -154,7 +144,7 @@ export interface RenameTableOperation extends MigrationOperation {
154
144
  /**
155
145
  * Add column operation.
156
146
  */
157
- export interface AddColumnOperation extends MigrationOperation {
147
+ export interface AddColumnOperation {
158
148
  type: 'addColumn';
159
149
  tableName: string;
160
150
  column: FullColumnDefinition;
@@ -162,7 +152,7 @@ export interface AddColumnOperation extends MigrationOperation {
162
152
  /**
163
153
  * Drop column operation.
164
154
  */
165
- export interface DropColumnOperation extends MigrationOperation {
155
+ export interface DropColumnOperation {
166
156
  type: 'dropColumn';
167
157
  tableName: string;
168
158
  columnName: string;
@@ -170,7 +160,7 @@ export interface DropColumnOperation extends MigrationOperation {
170
160
  /**
171
161
  * Alter column operation.
172
162
  */
173
- export interface AlterColumnOperation extends MigrationOperation {
163
+ export interface AlterColumnOperation {
174
164
  type: 'alterColumn';
175
165
  tableName: string;
176
166
  columnName: string;
@@ -179,7 +169,7 @@ export interface AlterColumnOperation extends MigrationOperation {
179
169
  /**
180
170
  * Rename column operation.
181
171
  */
182
- export interface RenameColumnOperation extends MigrationOperation {
172
+ export interface RenameColumnOperation {
183
173
  type: 'renameColumn';
184
174
  tableName: string;
185
175
  oldName: string;
@@ -188,7 +178,7 @@ export interface RenameColumnOperation extends MigrationOperation {
188
178
  /**
189
179
  * Create index operation.
190
180
  */
191
- export interface CreateIndexOperation extends MigrationOperation {
181
+ export interface CreateIndexOperation {
192
182
  type: 'createIndex';
193
183
  tableName: string;
194
184
  index: IndexDefinition;
@@ -197,7 +187,7 @@ export interface CreateIndexOperation extends MigrationOperation {
197
187
  /**
198
188
  * Drop index operation.
199
189
  */
200
- export interface DropIndexOperation extends MigrationOperation {
190
+ export interface DropIndexOperation {
201
191
  type: 'dropIndex';
202
192
  tableName: string;
203
193
  indexName: string;
@@ -206,7 +196,7 @@ export interface DropIndexOperation extends MigrationOperation {
206
196
  /**
207
197
  * Add foreign key operation.
208
198
  */
209
- export interface AddForeignKeyOperation extends MigrationOperation {
199
+ export interface AddForeignKeyOperation {
210
200
  type: 'addForeignKey';
211
201
  tableName: string;
212
202
  foreignKey: ForeignKeySchema;
@@ -214,7 +204,7 @@ export interface AddForeignKeyOperation extends MigrationOperation {
214
204
  /**
215
205
  * Drop foreign key operation.
216
206
  */
217
- export interface DropForeignKeyOperation extends MigrationOperation {
207
+ export interface DropForeignKeyOperation {
218
208
  type: 'dropForeignKey';
219
209
  tableName: string;
220
210
  constraintName: string;
@@ -222,7 +212,7 @@ export interface DropForeignKeyOperation extends MigrationOperation {
222
212
  /**
223
213
  * Raw SQL operation (escape hatch).
224
214
  */
225
- export interface RawSqlOperation extends MigrationOperation {
215
+ export interface RawSqlOperation {
226
216
  type: 'raw';
227
217
  sql: string;
228
218
  }
@@ -387,7 +377,7 @@ export interface IAlterTableBuilder {
387
377
  * Interface for the main migration builder.
388
378
  */
389
379
  export interface IMigrationBuilder {
390
- /** Create a new table */
380
+ /** Create a table as `callback` declares it; on MongoDB a collection, whose callback declares only indexes. */
391
381
  createTable(name: string, callback: (table: ITableBuilder) => void): Promise<void>;
392
382
  /** Drop a table */
393
383
  dropTable(name: string, options?: {
@@ -420,6 +410,6 @@ export interface IMigrationBuilder {
420
410
  }): Promise<void>;
421
411
  /** Drop a foreign key */
422
412
  dropForeignKey(tableName: string, constraintName: string): Promise<void>;
423
- /** Execute raw SQL */
413
+ /** Execute raw SQL, the escape hatch for anything the builder does not model. */
424
414
  raw(sql: string): Promise<void>;
425
415
  }
@@ -1,11 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import type { ForeignKeyAction } from '../schema/types.js';
3
- import type { Config, MigratorDialect } from '../type/index.js';
2
+ import type { Config } from '../type/index.js';
4
3
  import { Migrator } from './migrator.js';
5
- import { createSchemaGeneratorAsync } from './schemaGeneratorAsync.js';
6
- /** Sync helper for SQL dialects only; returns `undefined` for MongoDB - use {@link createSchemaGeneratorAsync}. */
7
- export declare function getSchemaGenerator(dialect: MigratorDialect, defaultForeignKeyAction?: ForeignKeyAction): import("./schemaGenerator.js").SqlSchemaGenerator | undefined;
8
- export { createSchemaGeneratorAsync };
9
4
  export declare function main(args?: string[]): Promise<void>;
10
5
  export declare function runUp(migrator: Migrator, args: string[]): Promise<void>;
11
6
  export declare function runDown(migrator: Migrator, args: string[]): Promise<void>;
@@ -19,5 +14,5 @@ export declare function runGenerateFromEntities(migrator: Migrator, args: string
19
14
  */
20
15
  export declare function runTypes(migrator: Migrator, args: string[]): void;
21
16
  export declare function runSync(migrator: Migrator, args: string[], config: Partial<Config>): Promise<void>;
22
- export declare function runGenerateFromDb(migrator: Migrator, args: string[], config: Partial<Config>): Promise<void>;
17
+ export declare function runGenerateFromDb(migrator: Migrator, args: string[]): Promise<void>;
23
18
  export declare function runDriftCheck(migrator: Migrator, config: Partial<Config>): Promise<void>;