uql-orm 0.65.1 → 0.67.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 (193) hide show
  1. package/dist/browser/querier/httpQuerier.js +1 -8
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/bunSql/bunSql.util.d.ts +2 -6
  4. package/dist/bunSql/bunSql.util.js +2 -6
  5. package/dist/bunSql/bunSqlQuerier.d.ts +2 -5
  6. package/dist/bunSql/bunSqlQuerier.js +2 -5
  7. package/dist/cockroachdb/cockroachDialect.d.ts +4 -13
  8. package/dist/cockroachdb/cockroachDialect.js +4 -13
  9. package/dist/context/context.browser.js +2 -10
  10. package/dist/context/context.d.ts +4 -17
  11. package/dist/context/context.js +4 -17
  12. package/dist/dialect/abstractDialect.d.ts +4 -19
  13. package/dist/dialect/abstractDialect.js +2 -20
  14. package/dist/dialect/abstractSqlDialect.d.ts +47 -212
  15. package/dist/dialect/abstractSqlDialect.js +68 -222
  16. package/dist/dialect/aliases.d.ts +2 -12
  17. package/dist/dialect/aliases.js +4 -12
  18. package/dist/dialect/hydrateColumn.d.ts +2 -6
  19. package/dist/dialect/hydrateColumn.js +3 -13
  20. package/dist/dialect/jsonArrayElemMatchUtils.d.ts +1 -7
  21. package/dist/dialect/jsonArrayElemMatchUtils.js +1 -7
  22. package/dist/dialect/jsonSql.d.ts +6 -27
  23. package/dist/dialect/jsonSql.js +6 -27
  24. package/dist/dialect/mergeSqlDialect.d.ts +4 -22
  25. package/dist/dialect/mergeSqlDialect.js +4 -22
  26. package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -37
  27. package/dist/dialect/mysqlLikeSqlDialect.js +35 -51
  28. package/dist/dialect/pgLikeSqlDialect.d.ts +8 -22
  29. package/dist/dialect/pgLikeSqlDialect.js +36 -39
  30. package/dist/dialect/queryContext.d.ts +4 -22
  31. package/dist/dialect/queryContext.js +4 -22
  32. package/dist/dialect/queryJoins.d.ts +3 -12
  33. package/dist/dialect/queryJoins.js +3 -12
  34. package/dist/dialect/vectorCast.d.ts +2 -12
  35. package/dist/dialect/vectorCast.js +3 -19
  36. package/dist/dialect/vectorSqlDialect.d.ts +8 -38
  37. package/dist/dialect/vectorSqlDialect.js +7 -38
  38. package/dist/entity/decorator/bag.d.ts +6 -19
  39. package/dist/entity/decorator/bag.js +6 -22
  40. package/dist/entity/decorator/entity.d.ts +5 -10
  41. package/dist/entity/decorator/entity.js +2 -7
  42. package/dist/entity/decorator/members.d.ts +10 -31
  43. package/dist/entity/decorator/members.js +3 -12
  44. package/dist/entity/metadata/definition.d.ts +5 -21
  45. package/dist/entity/metadata/definition.js +69 -91
  46. package/dist/http/handler.d.ts +2 -14
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +3 -1
  49. package/dist/libsql/libsqlDialect.d.ts +1 -8
  50. package/dist/libsql/libsqlDialect.js +1 -8
  51. package/dist/maria/mariaDialect.d.ts +3 -5
  52. package/dist/maria/mariaDialect.js +5 -5
  53. package/dist/maria/mariadbQuerier.js +2 -2
  54. package/dist/maria/mariadbQuerierPool.js +1 -6
  55. package/dist/migrate/builder/migrationBuilder.js +3 -19
  56. package/dist/migrate/builder/splitSqlStatements.d.ts +1 -14
  57. package/dist/migrate/builder/splitSqlStatements.js +2 -22
  58. package/dist/migrate/builder/types.d.ts +2 -15
  59. package/dist/migrate/cli-config.js +2 -11
  60. package/dist/migrate/cli.js +2 -7
  61. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -15
  62. package/dist/migrate/codegen/entityCodeGenerator.js +15 -44
  63. package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -8
  64. package/dist/migrate/codegen/fieldOptionsSource.js +3 -22
  65. package/dist/migrate/ddl/indexDdl.d.ts +2 -5
  66. package/dist/migrate/ddl/indexDdl.js +2 -5
  67. package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -13
  68. package/dist/migrate/ddl/pgIndexDdl.js +3 -13
  69. package/dist/migrate/generator/definitionToNode.d.ts +2 -9
  70. package/dist/migrate/generator/definitionToNode.js +3 -17
  71. package/dist/migrate/generator/indexNodeToSchema.d.ts +2 -3
  72. package/dist/migrate/generator/indexNodeToSchema.js +2 -3
  73. package/dist/migrate/generator/mongoCommand.d.ts +1 -8
  74. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -8
  75. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -8
  76. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +6 -26
  77. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +9 -41
  78. package/dist/migrate/introspection/baseSqlIntrospector.js +0 -1
  79. package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
  80. package/dist/migrate/introspection/mongoIntrospector.js +48 -46
  81. package/dist/migrate/introspection/mssqlIntrospector.d.ts +4 -4
  82. package/dist/migrate/introspection/mssqlIntrospector.js +18 -27
  83. package/dist/migrate/introspection/mysqlIntrospector.d.ts +7 -2
  84. package/dist/migrate/introspection/mysqlIntrospector.js +16 -14
  85. package/dist/migrate/introspection/postgresIntrospector.d.ts +24 -9
  86. package/dist/migrate/introspection/postgresIntrospector.js +68 -59
  87. package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -1
  88. package/dist/migrate/introspection/sqliteIntrospector.js +8 -10
  89. package/dist/migrate/migrator.d.ts +9 -53
  90. package/dist/migrate/migrator.js +32 -65
  91. package/dist/migrate/schemaGenerator.d.ts +20 -66
  92. package/dist/migrate/schemaGenerator.js +32 -93
  93. package/dist/mongo/mongoDialect.d.ts +21 -53
  94. package/dist/mongo/mongoDialect.js +25 -70
  95. package/dist/mongo/mongodbQuerier.d.ts +5 -8
  96. package/dist/mongo/mongodbQuerier.js +31 -65
  97. package/dist/mssql/mssqlDialect.d.ts +8 -34
  98. package/dist/mssql/mssqlDialect.js +37 -51
  99. package/dist/mssql/mssqlQuerier.d.ts +37 -4
  100. package/dist/mssql/mssqlQuerier.js +2 -2
  101. package/dist/mssql/mssqlWireTypes.d.ts +2 -14
  102. package/dist/mssql/mssqlWireTypes.js +2 -14
  103. package/dist/nestjs/uqlModule.js +2 -7
  104. package/dist/pglite/pgliteQuerier.d.ts +1 -9
  105. package/dist/pglite/pgliteQuerierPool.d.ts +4 -26
  106. package/dist/pglite/pgliteQuerierPool.js +3 -18
  107. package/dist/postgres/abstractPgQuerierPool.d.ts +1 -8
  108. package/dist/postgres/abstractPgQuerierPool.js +1 -8
  109. package/dist/postgres/pgNumericTypes.d.ts +3 -26
  110. package/dist/postgres/pgNumericTypes.js +3 -26
  111. package/dist/postgres/postgresDialect.d.ts +4 -10
  112. package/dist/postgres/postgresDialect.js +4 -10
  113. package/dist/querier/abstractQuerier.d.ts +35 -103
  114. package/dist/querier/abstractQuerier.js +105 -201
  115. package/dist/querier/abstractSharedHandleQuerierPool.d.ts +3 -17
  116. package/dist/querier/abstractSharedHandleQuerierPool.js +3 -17
  117. package/dist/querier/abstractSqlQuerier.d.ts +15 -36
  118. package/dist/querier/abstractSqlQuerier.js +49 -131
  119. package/dist/schema/canonicalType.d.ts +3 -21
  120. package/dist/schema/canonicalType.js +22 -67
  121. package/dist/schema/dependencyGraph.d.ts +2 -8
  122. package/dist/schema/dependencyGraph.js +2 -32
  123. package/dist/schema/index.d.ts +1 -25
  124. package/dist/schema/index.js +0 -26
  125. package/dist/schema/indexColumns.d.ts +1 -8
  126. package/dist/schema/indexColumns.js +1 -8
  127. package/dist/schema/indexDifferences.d.ts +7 -40
  128. package/dist/schema/indexDifferences.js +6 -31
  129. package/dist/schema/schemaAST.d.ts +8 -175
  130. package/dist/schema/schemaAST.js +13 -365
  131. package/dist/schema/schemaASTBuilder.d.ts +2 -24
  132. package/dist/schema/schemaASTBuilder.js +6 -41
  133. package/dist/schema/schemaASTDiffer.d.ts +6 -46
  134. package/dist/schema/schemaASTDiffer.js +8 -56
  135. package/dist/schema/types.d.ts +5 -61
  136. package/dist/schema/types.js +3 -6
  137. package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -8
  138. package/dist/sqlite/localSqliteQuerierPool.d.ts +1 -7
  139. package/dist/sqlite/localSqliteQuerierPool.js +1 -7
  140. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +2 -7
  141. package/dist/sqlite/nodeSqliteQuerierPool.js +2 -7
  142. package/dist/sqlite/sqliteDialect.d.ts +5 -20
  143. package/dist/sqlite/sqliteDialect.js +29 -35
  144. package/dist/turso/tursoDialect.d.ts +4 -6
  145. package/dist/turso/tursoDialect.js +4 -6
  146. package/dist/turso/tursoLocalQuerierPool.d.ts +1 -7
  147. package/dist/turso/tursoLocalQuerierPool.js +1 -7
  148. package/dist/turso/tursoQuerierPool.d.ts +2 -6
  149. package/dist/turso/tursoQuerierPool.js +2 -6
  150. package/dist/turso/tursoSessionQuerier.d.ts +1 -7
  151. package/dist/turso/tursoSessionQuerier.js +1 -7
  152. package/dist/type/dialect.d.ts +42 -94
  153. package/dist/type/dialect.js +3 -13
  154. package/dist/type/entity.d.ts +189 -551
  155. package/dist/type/entity.js +26 -9
  156. package/dist/type/logger.d.ts +2 -14
  157. package/dist/type/migration.d.ts +9 -38
  158. package/dist/type/querier.d.ts +9 -28
  159. package/dist/type/querierPool.d.ts +4 -26
  160. package/dist/type/query.d.ts +28 -78
  161. package/dist/type/query.js +2 -7
  162. package/dist/type/queryAggregate.d.ts +18 -98
  163. package/dist/type/queryRaw.d.ts +1 -8
  164. package/dist/type/queryRaw.js +1 -8
  165. package/dist/type/queryWhere.d.ts +13 -61
  166. package/dist/type/universalQuerier.d.ts +18 -105
  167. package/dist/type/utility.d.ts +12 -24
  168. package/dist/type/vector.d.ts +8 -38
  169. package/dist/type/vector.js +1 -1
  170. package/dist/type/wire.d.ts +2 -5
  171. package/dist/util/dialect.util.d.ts +9 -27
  172. package/dist/util/dialect.util.js +10 -27
  173. package/dist/util/field.util.d.ts +5 -37
  174. package/dist/util/field.util.js +7 -50
  175. package/dist/util/fieldOption.util.d.ts +7 -15
  176. package/dist/util/fieldOption.util.js +1 -1
  177. package/dist/util/filters.util.d.ts +2 -5
  178. package/dist/util/filters.util.js +2 -5
  179. package/dist/util/logger.d.ts +2 -6
  180. package/dist/util/logger.js +2 -6
  181. package/dist/util/object.util.d.ts +2 -6
  182. package/dist/util/object.util.js +1 -5
  183. package/dist/util/raw.d.ts +3 -23
  184. package/dist/util/relationQuery.util.d.ts +3 -14
  185. package/dist/util/relationQuery.util.js +3 -14
  186. package/dist/util/rowKey.util.d.ts +2 -10
  187. package/dist/util/rowKey.util.js +2 -10
  188. package/dist/util/sql.util.d.ts +6 -37
  189. package/dist/util/sql.util.js +13 -73
  190. package/dist/util/sqlLiteral.d.ts +2 -13
  191. package/dist/util/sqlLiteral.js +8 -13
  192. package/dist/util/string.util.js +0 -2
  193. package/package.json +4 -4
@@ -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 { entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, lowerFirst, memberRefs, normalizeIndexColumn, upperFirst, definedEntries, } from '../../util/index.js';
3
+ import { entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, memberRefs, normalizeIndexColumn, 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
@@ -108,7 +108,9 @@ export function defineFilter(entity, name, opts) {
108
108
  if (name === SOFT_DELETE_FILTER) {
109
109
  throw TypeError(`'${entity.name}' filter name '${SOFT_DELETE_FILTER}' is reserved; it is auto-registered from @Field({ softDelete })`);
110
110
  }
111
- if (opts.security && opts.onMissing === 'skip') {
111
+ // Widened for a caller the types did not reach, which is the only one this can refuse.
112
+ const { security, onMissing } = opts;
113
+ if (security && onMissing === 'skip') {
112
114
  throw TypeError(`'${entity.name}' security filter '${name}' cannot use onMissing: 'skip' (it must fail closed)`);
113
115
  }
114
116
  (meta.filters ??= {})[name] = opts;
@@ -173,11 +175,8 @@ export function defineEntity(entity, opts = {}) {
173
175
  if (!hasKeys(meta.fields)) {
174
176
  throw TypeError(`'${entity.name}' must have fields`);
175
177
  }
176
- // A later call composes onto the entity, so saying nothing about the table retracts nothing - which
177
- // is why `derivedName` is only ever *set*, never recomputed from what a previous call left.
178
- // It records that the class name stood in, telling a naming strategy there is something to derive;
179
- // comparing the two cannot, since an entity may name its table exactly what its class is called and
180
- // a spec's minted class is named after its table.
178
+ // A later call composes onto the entity, so a name is only ever set, and `derivedName` records that
179
+ // the class name stood in, which is what a naming strategy derives from.
181
180
  if (opts.name !== undefined) {
182
181
  meta.name = opts.name;
183
182
  meta.derivedName = false;
@@ -249,27 +248,11 @@ export function relationOf(meta, key) {
249
248
  }
250
249
  return relation;
251
250
  }
252
- /**
253
- * Whether the caller named every column of the row's primary key, so {@link idOf} can name the row.
254
- *
255
- * `!= null` rather than falsiness: `0` and an empty string are ids a row can legitimately carry, and
256
- * reading them as "no id" is how a write of that row turned into a second insert. Distinct from
257
- * "does the row carry this column", which an insert asks of `undefined` alone because that is what
258
- * decides whether the column appears in its `VALUES` list at all.
259
- */
251
+ /** Whether the row names every column of its primary key, `0` and `''` included. */
260
252
  export function namesKey(meta, row) {
261
253
  return meta.ids.every((key) => row[key] != null);
262
254
  }
263
- /**
264
- * A row's primary key: the value itself for a single key, an object carrying every key for a
265
- * composite - which is {@link WrittenId}, and reads as the {@link EntityId} a `$where` takes.
266
- *
267
- * What a settled write names its rows by, and what a write hands back. Naming a composite row by one
268
- * of its columns would address every row agreeing on that one.
269
- *
270
- * `WrittenId` does not reduce for an unresolved `E`, so which branch this entity is in cannot be
271
- * proven here, only checked - which is what `ids.length` does.
272
- */
255
+ /** A row's primary key: its value, or a map of every column on a composite, checked at run time. */
273
256
  export function idOf(meta, row) {
274
257
  const { ids } = meta;
275
258
  const id = ids.length === 1 ? row[ids[0]] : Object.fromEntries(ids.map((key) => [key, row[key]]));
@@ -326,9 +309,13 @@ function registeredMeta(entity) {
326
309
  function fillRelations(meta) {
327
310
  for (const [relKey, relation] of definedEntries(meta.relations)) {
328
311
  const at = `'${meta.entity.name}.${relKey}'`;
329
- if (!settledReferences(at, meta, relKey, relation).length) {
312
+ const references = settledReferences(at, meta, relKey, relation);
313
+ if (!references.length) {
330
314
  throw new TypeError(`${at} has no columns to join on.`);
331
315
  }
316
+ if (!relation.through) {
317
+ assertJoins(at, meta, relation, references);
318
+ }
332
319
  }
333
320
  // A column `references` names is a foreign key with or without a relation over it, and one cannot point
334
321
  // at a composite key: refused on first read, as a relation that cannot join is, not at the schema build.
@@ -356,18 +343,11 @@ function settledReferences(at, meta, relKey, relOpts) {
356
343
  return fillInverseSide(at, meta, relOpts, mappedBy);
357
344
  if (through)
358
345
  return fillThrough(at, meta, relOpts, through);
359
- return fillOwningSide(at, meta, relKey, relOpts);
360
- }
361
- /**
362
- * Settles each relation joining on its own entity's columns, every one but an inverse side and a `through`:
363
- * the columns they create and the foreign keys they hold are what another entity reads off this one.
364
- */
365
- function settleOwnColumns(meta) {
366
- for (const [relKey, relation] of definedEntries(meta.relations)) {
367
- if (!relation.mappedBy && !relation.through) {
368
- settledReferences(`'${meta.entity.name}.${relKey}'`, meta, relKey, relation);
369
- }
370
- }
346
+ throw new TypeError(isToManyRelation(relOpts)
347
+ ? `${at} is a to-many relation with no way to join: it needs 'mappedBy' (the member on the other side), ` +
348
+ "'through' (a junction entity), or 'references' (the columns)."
349
+ : `${at} needs 'references', the foreign key column it joins by, or 'mappedBy', the member on the other ` +
350
+ 'side holding it.');
371
351
  }
372
352
  /**
373
353
  * Each key of this entity, then each of the target, paired with the junction's one column referencing it.
@@ -382,48 +362,9 @@ function fillThrough(at, meta, relOpts, through) {
382
362
  ];
383
363
  return relOpts.references;
384
364
  }
385
- function fillOwningSide(at, meta, relKey, relOpts) {
386
- if (isToManyRelation(relOpts)) {
387
- throw new TypeError(`${at} is a to-many relation with no way to join: it needs 'mappedBy' (the field on the other side), ` +
388
- "'through' (a junction entity), or 'references' (the columns).");
389
- }
390
- const relMeta = ensureMeta(relOpts.entity());
391
- // `<rel>Id` for the one-key case it has always been; `<rel><Key>` per column otherwise. Both name a
392
- // property, so both are spelled from the referenced *property* - a column name is what the naming
393
- // strategy makes of this afterwards.
394
- const sole = relMeta.ids.length === 1;
395
- const references = relMeta.ids.map((key) => ({
396
- local: sole ? `${relKey}Id` : `${relKey}${upperFirst(key)}`,
397
- foreign: key,
398
- }));
399
- // A column the entity declares would be joined by its name alone, so renaming either one would leave
400
- // the other behind, still compiling.
401
- const fields = meta.fields;
402
- if (references.some(({ local }) => fields[local])) {
403
- const own = lowerFirst(meta.entity.name);
404
- throw new TypeError(`${at} joins ${references.map(({ local }) => `'${local}'`).join(', ')} by name, which a rename does not ` +
405
- `follow: link them with ${sole ? `'references: (${own}) => ${own}.${references[0].local}'` : "'references' pairs"}.`);
406
- }
407
- // `typeFromReference` so schema generation resolves the referenced primary key's exact type
408
- // (columnType, length, chained keys) rather than trusting the fallback, as it does for an
409
- // explicit `@Field({ references })`.
410
- for (const { local, foreign } of references) {
411
- fields[local] = {
412
- name: local,
413
- type: fieldOf(relMeta, foreign).type ?? Number,
414
- references: relOpts.entity,
415
- referencedKey: foreign,
416
- typeFromReference: true,
417
- };
418
- }
419
- relOpts.references = references;
420
- return references;
421
- }
422
365
  function fillInverseSide(at, meta, relOpts, mappedBy) {
423
366
  const relMeta = registeredMeta(relOpts.entity());
424
367
  const other = `'${relMeta.entity.name}.${mappedBy}'`;
425
- // The other side's own columns first: they declare, or create, what this side is mapped by.
426
- settleOwnColumns(relMeta);
427
368
  if (relMeta.fields[mappedBy]) {
428
369
  if (meta.ids.length > 1) {
429
370
  throw new TypeError(`${at} is mapped by ${other}, one column, but the primary key of ` +
@@ -441,6 +382,10 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
441
382
  if (owner.mappedBy) {
442
383
  throw new TypeError(`${at} is mapped by ${other}, an inverse side too, so neither owns the foreign key.`);
443
384
  }
385
+ const ownerTarget = owner.entity();
386
+ if (!isA(meta.entity, ownerTarget)) {
387
+ throw new TypeError(`${at} is mapped by ${other}, a relation to '${ownerTarget.name}', not to '${meta.entity.name}'.`);
388
+ }
444
389
  const ownerReferences = settledReferences(other, relMeta, mappedBy, owner);
445
390
  // Two different flips: a junction's pairs are the owner's group followed by ours, so the two groups
446
391
  // swap - `toReversed` would also reverse each group, pairing a composite's columns crosswise. A
@@ -452,16 +397,52 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
452
397
  relOpts.through = owner.through;
453
398
  return relOpts.references;
454
399
  }
400
+ /**
401
+ * Refuses a join on a column either entity does not store, and a one-column join whose foreign key, on
402
+ * whichever side holds it, points at another entity: it would match unrelated rows by their keys.
403
+ */
404
+ function assertJoins(at, meta, relOpts, pairs) {
405
+ const target = registeredMeta(relOpts.entity());
406
+ // Only the owning side of a to-one holds its foreign key; an inverse side and a to-many join on the target's.
407
+ const holdsLocally = !relOpts.mappedBy && !isToManyRelation(relOpts);
408
+ const sides = [
409
+ { meta: columnsOf(meta), keys: pairs.map(({ local }) => local), joins: target.entity, holds: holdsLocally },
410
+ { meta: columnsOf(target), keys: pairs.map(({ foreign }) => foreign), joins: meta.entity, holds: !holdsLocally },
411
+ ];
412
+ for (const side of sides) {
413
+ for (const key of side.keys) {
414
+ const column = `'${side.meta.entity.name}.${key}'`;
415
+ const field = side.meta.fields[key];
416
+ if (!field || isInlinedExpression(field)) {
417
+ throw new TypeError(`${at} joins ${column}, which is not a column: declare it with '@Field'.`);
418
+ }
419
+ const referenced = side.holds && pairs.length === 1 ? field.references?.() : undefined;
420
+ if (referenced && !isA(side.joins, referenced)) {
421
+ throw new TypeError(`${at} joins ${column}, a foreign key to '${referenced.name}', not to '${side.joins.name}'.`);
422
+ }
423
+ }
424
+ }
425
+ }
426
+ /** `meta` with its fields read by any name, as a join's columns come. */
427
+ function columnsOf(meta) {
428
+ return meta;
429
+ }
430
+ /** Whether `entity` is `base` or extends it, as an entity inheriting a relation does. */
431
+ function isA(entity, base) {
432
+ return entity === base || entity.prototype instanceof base;
433
+ }
455
434
  /**
456
435
  * The foreign keys an entity holds: each owning to-one's columns, and each `@Field({ references })` no
457
436
  * relation joins on, as the many-to-one it describes, once its target has registered a key. What the
458
- * schema build constrains and a junction joins by, read once the relations holding them are settled.
437
+ * schema build constrains and a junction joins by, settling the relations holding them first.
459
438
  */
460
439
  export function foreignKeysOf(meta) {
461
- settleOwnColumns(meta);
462
440
  const owning = definedEntries(meta.relations)
463
- .map(([, relation]) => relation)
464
- .filter((relation) => !relation.mappedBy && !relation.through && !isToManyRelation(relation));
441
+ .filter(([, relation]) => !relation.mappedBy && !relation.through && !isToManyRelation(relation))
442
+ .map(([relKey, relation]) => {
443
+ settledReferences(`'${meta.entity.name}.${relKey}'`, meta, relKey, relation);
444
+ return relation;
445
+ });
465
446
  const joined = new Set(owning.flatMap(({ references }) => references.map(({ local }) => local)));
466
447
  const columns = definedEntries(meta.fields).flatMap(([key, field]) => {
467
448
  if (!field.references || joined.has(key))
@@ -471,8 +452,8 @@ export function foreignKeysOf(meta) {
471
452
  return [];
472
453
  if (target.ids.length > 1) {
473
454
  throw new TypeError(`'${meta.entity.name}.${key}' cannot reference '${target.entity.name}', whose primary key is composite ` +
474
- `(${target.ids.join(', ')}): a column points at one. Use ` +
475
- `'@ManyToOne({ entity: () => ${target.entity.name} })', which declares one column per key.`);
455
+ `(${target.ids.join(', ')}): a column points at one. Declare a column per key and pair each with it ` +
456
+ `in a '@ManyToOne' to '${target.entity.name}'.`);
476
457
  }
477
458
  return [{ entity: field.references, cardinality: 'm1', references: [{ local: key, foreign: target.ids[0] }] }];
478
459
  });
@@ -486,9 +467,9 @@ function junctionReferences(at, junction, side) {
486
467
  const referenced = `'${side.entity.name}.${key}'`;
487
468
  if (!pair) {
488
469
  const declare = side.ids.length > 1
489
- ? `@ManyToOne({ entity: () => ${side.entity.name} })`
490
- : `@Field({ references: () => ${side.entity.name} })`;
491
- throw new TypeError(`${at} joins through '${junction.entity.name}', which has no column referencing ${referenced}: declare one, '${declare}'.`);
470
+ ? `a column per key, paired in a '@ManyToOne' to '${side.entity.name}'`
471
+ : `'@Field({ references: () => ${side.entity.name} })'`;
472
+ throw new TypeError(`${at} joins through '${junction.entity.name}', which has no column referencing ${referenced}: declare ${declare}.`);
492
473
  }
493
474
  if (others.length) {
494
475
  const columns = [pair, ...others].map(({ local }) => `'${local}'`).join(' and ');
@@ -503,11 +484,8 @@ function getIdKeys(meta) {
503
484
  return getKeys(meta.fields).filter((key) => meta.fields[key]?.isId);
504
485
  }
505
486
  /**
506
- * Merges `ancestor` and its own ancestors into `meta`, nearest first, so a further one never overwrites
507
- * a nearer. An `abstract class BaseEntity` carrying `@Field`s but no `@Entity()` has nobody to drain
508
- * its registrations, so do it here. Walking the *class* prototype chain rather than the metadata
509
- * object's is what makes this work on every transformer: tsc and esbuild chain metadata across
510
- * `extends`, SWC does not.
487
+ * Merges `ancestor` and its ancestors into `meta`, nearest first, draining an undecorated base's
488
+ * registrations. Walks the class chain, since not every compiler chains decorator metadata.
511
489
  */
512
490
  function inheritFrom(meta, ancestor) {
513
491
  for (let parent = ancestor; parent && parent !== Object; parent = parentOf(parent)) {
@@ -29,13 +29,7 @@ export type HookContext<E extends object, Ctx = unknown> = {
29
29
  readonly meta: EntityMeta<E>;
30
30
  readonly op: CrudOperation;
31
31
  readonly method: HttpMethod;
32
- /**
33
- * parsed query - mutate in place or reassign to shape it (e.g. force a `$select`, inject a
34
- * `$sort`). For actual tenant/row-level scoping, prefer `@Filter(..., { security: true })` on
35
- * the entity instead: unlike a hook mutation, it is AND-merged (a client `$where` on the same
36
- * key cannot silently win), fails closed when its context is missing, and applies uniformly
37
- * across every query path including joined relations.
38
- */
32
+ /** The parsed query, to reshape in place. Scope rows with a `security` filter instead, which a client cannot override. */
39
33
  query: Query<E>;
40
34
  /**
41
35
  * request payload - reassignable for sanitization or field injection.
@@ -51,13 +45,7 @@ export type ResponseHook<Ctx = unknown> = <E extends object>(ctx: HookContext<E,
51
45
  export type RequestHandlerOptions<Ctx = unknown> = {
52
46
  include?: Type<object>[];
53
47
  exclude?: Type<object>[];
54
- /**
55
- * The URL segment an entity is addressed by, defaulting to its kebab-cased class name.
56
- *
57
- * State it where the default cannot serve: a build that minifies class names renames every route,
58
- * and two entities mapping one table in different schemas collide on one. The browser client takes
59
- * the same option, so both ends can read one map.
60
- */
48
+ /** The URL segment an entity is addressed by, its kebab-cased class name by default; the browser client takes the same option. */
61
49
  entityPath?: (entity: Type<unknown>) => string;
62
50
  /**
63
51
  * Allow augment any kind of request before it runs. Hooks may be async
package/dist/index.d.ts CHANGED
@@ -4,4 +4,6 @@ export * from './entity/index.js';
4
4
  export * from './namingStrategy/index.js';
5
5
  export * from './querier/index.js';
6
6
  export * from './type/index.js';
7
- export * from './util/index.js';
7
+ export { withDeleted } from './util/filters.util.js';
8
+ export { DefaultLogger } from './util/logger.js';
9
+ export { raw, refs } from './util/raw.js';
package/dist/index.js CHANGED
@@ -4,4 +4,6 @@ export * from './entity/index.js';
4
4
  export * from './namingStrategy/index.js';
5
5
  export * from './querier/index.js';
6
6
  export * from './type/index.js';
7
- export * from './util/index.js';
7
+ export { withDeleted } from './util/filters.util.js';
8
+ export { DefaultLogger } from './util/logger.js';
9
+ export { raw, refs } from './util/raw.js';
@@ -7,13 +7,6 @@ import type { VectorDistance, VectorMetric } from '../type/index.js';
7
7
  * functions, which `TursoDialect` inherits.
8
8
  */
9
9
  export declare class LibsqlDialect extends SqliteDialect {
10
- /**
11
- * libSQL has vector search built in, under its own names, so the sqlite-vec `vec_distance_*`
12
- * functions this dialect would otherwise inherit are never present.
13
- *
14
- * @remarks `inner` and `l1` are left out: `vector_distance_dot` only exists in the newer Rust
15
- * engine (see `TursoLocalDialect`) and no libSQL build has an L1 metric. Both raise the same
16
- * "does not support vector distance metric" error as any other unsupported metric.
17
- */
10
+ /** libSQL's built-in vector functions; no `inner` (only the Rust engine has it) and no `l1`. */
18
11
  readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
19
12
  }
@@ -6,14 +6,7 @@ import { SqliteDialect } from '../sqlite/sqliteDialect.js';
6
6
  * functions, which `TursoDialect` inherits.
7
7
  */
8
8
  export class LibsqlDialect extends SqliteDialect {
9
- /**
10
- * libSQL has vector search built in, under its own names, so the sqlite-vec `vec_distance_*`
11
- * functions this dialect would otherwise inherit are never present.
12
- *
13
- * @remarks `inner` and `l1` are left out: `vector_distance_dot` only exists in the newer Rust
14
- * engine (see `TursoLocalDialect`) and no libSQL build has an L1 metric. Both raise the same
15
- * "does not support vector distance metric" error as any other unsupported metric.
16
- */
9
+ /** libSQL's built-in vector functions; no `inner` (only the Rust engine has it) and no `l1`. */
17
10
  vectorMetrics = new Map([
18
11
  ['cosine', { fn: 'vector_distance_cos' }],
19
12
  ['l2', { fn: 'vector_distance_l2' }],
@@ -1,16 +1,14 @@
1
1
  import { type RelationRows } from '../dialect/abstractSqlDialect.js';
2
2
  import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
3
- import type { DialectFeatures, EntityMeta, FieldOptions, Query, QueryContext, Type, VectorDistance, VectorMetric } from '../type/index.js';
3
+ import type { EntityMeta, FieldOptions, Query, QueryContext, SqlDialectFeatures, Type, VectorDistance, VectorMetric } from '../type/index.js';
4
4
  export declare class MariaDialect extends MysqlLikeSqlDialect {
5
5
  readonly dialectName = "mariadb";
6
6
  readonly insertIdSource = "returning";
7
- /** MariaDB has no `FOR ... OF`, so a lock cannot be narrowed to one table of a join. */
8
- readonly supportsLockOf = false;
9
7
  /**
10
8
  * Unlike MySQL: `VECTOR(n)` takes its dimension, every column of a vector index has to be NOT NULL,
11
- * and `CREATE INDEX` takes `IF NOT EXISTS` - which MySQL's grammar has no place for.
9
+ * `CREATE INDEX` takes `IF NOT EXISTS`, and a lock cannot be narrowed to one table of a join.
12
10
  */
13
- protected readonly featureOverrides: Partial<DialectFeatures>;
11
+ readonly features: SqlDialectFeatures;
14
12
  /**
15
13
  * A derived table here reads no column of the statement around it, so the aggregate reads the
16
14
  * related table itself, and orders and pages inside `JSON_ARRAYAGG`, which takes both.
@@ -1,6 +1,6 @@
1
1
  import { relationTermKey } from '../dialect/abstractSqlDialect.js';
2
2
  import { jsonPath } from '../dialect/jsonSql.js';
3
- import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
3
+ import { MYSQL_FEATURES, MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
4
4
  import { getMeta } from '../entity/index.js';
5
5
  import { columnFamily } from '../util/field.util.js';
6
6
  import { MARIA_VECTOR_METRICS } from './mariaVectorMetrics.js';
@@ -8,16 +8,16 @@ export class MariaDialect extends MysqlLikeSqlDialect {
8
8
  dialectName = 'mariadb';
9
9
  // MariaDB 10.5+ has `INSERT ... RETURNING`, so ids come back exact per row - the upsert's too.
10
10
  insertIdSource = 'returning';
11
- /** MariaDB has no `FOR ... OF`, so a lock cannot be narrowed to one table of a join. */
12
- supportsLockOf = false;
13
11
  /**
14
12
  * Unlike MySQL: `VECTOR(n)` takes its dimension, every column of a vector index has to be NOT NULL,
15
- * and `CREATE INDEX` takes `IF NOT EXISTS` - which MySQL's grammar has no place for.
13
+ * `CREATE INDEX` takes `IF NOT EXISTS`, and a lock cannot be narrowed to one table of a join.
16
14
  */
17
- featureOverrides = {
15
+ features = {
16
+ ...MYSQL_FEATURES,
18
17
  vectorSupportsLength: true,
19
18
  vectorIndexRequiresNotNull: true,
20
19
  indexIfNotExists: true,
20
+ rowLockOf: false,
21
21
  };
22
22
  /**
23
23
  * A derived table here reads no column of the statement around it, so the aggregate reads the
@@ -7,8 +7,8 @@ export class MariadbQuerier extends AbstractPoolQuerier {
7
7
  }
8
8
  async internalRun(query, values) {
9
9
  const res = await this.getConn().query(query, values);
10
- // MariaDB may not set `affectedRows` when RETURNING is used; fall back to row count.
11
- const changes = res.affectedRows ?? res.length ?? 0;
10
+ // An OK packet reports `affectedRows`; a `RETURNING` statement answers rows instead, and counts by them.
11
+ const changes = res.affectedRows ?? res.length;
12
12
  const rows = res.length ? Array.from(res, decodeBigInts) : [];
13
13
  return this.buildUpdateResult({ rows, changes, upsertStatus: res.affectedRows });
14
14
  }
@@ -11,12 +11,7 @@ export class MariadbQuerierPool extends AbstractSqlQuerierPool {
11
11
  // BIGINT stays the driver's `bigint`, which `MariadbQuerier` decodes by the rule every driver here
12
12
  // shares (`decodeWideNumber`) - not `bigIntAsNumber`, which rounds past 2^53 without a word.
13
13
  this.pool = createPool(opts);
14
- // `mariadb`'s own `createPool` already attaches a silent no-op 'error'
15
- // listener (so a dropped connection can't crash the process), but its
16
- // `Pool` type only declares `on` for 'acquire' | 'connection' | 'enqueue'
17
- // | 'release' - 'error' genuinely fires at runtime (see `lib/pool.js`)
18
- // but isn't in the declaration, hence the cast. Re-attaching our own
19
- // listener here just makes the error visible instead of a silent no-op.
14
+ // `mariadb` fires 'error' at runtime without declaring it, hence the cast; this makes it visible.
20
15
  attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error', extra?.logger);
21
16
  }
22
17
  async getQuerier() {
@@ -20,13 +20,7 @@ function createIndexOperation(tableName, columns, options = {}) {
20
20
  },
21
21
  };
22
22
  }
23
- /**
24
- * The one shape of an `addForeignKey` operation, `NO ACTION` defaults included.
25
- *
26
- * Three callers build it and differ only in what they do with the result: the table builder records
27
- * it through its parent, the recorder records it directly, and the executing builder also runs it.
28
- * Spelled out three times, a changed default would have had to be found in all three.
29
- */
23
+ /** An `addForeignKey` operation, `NO ACTION` defaults included, for the three builders that record one. */
30
24
  function addForeignKeyOperation(tableName, columns, target, options = {}) {
31
25
  return {
32
26
  type: 'addForeignKey',
@@ -40,21 +34,11 @@ function addForeignKeyOperation(tableName, columns, target, options = {}) {
40
34
  },
41
35
  };
42
36
  }
43
- /**
44
- * Declare one column through the same vocabulary `createTable` uses. A throwaway {@link TableBuilder}
45
- * is that vocabulary: `addColumn`/`alterColumn` used to take a bare {@link IColumnBuilder}, which
46
- * cannot express a type, so every column they recorded was hard-coded `VARCHAR`.
47
- */
37
+ /** One column declared through `createTable`'s vocabulary, a throwaway {@link TableBuilder}, so its type is stated. */
48
38
  function buildOneColumn(callback) {
49
39
  return callback(new TableBuilder('')).build();
50
40
  }
51
- /**
52
- * Collects the operations one `alterTable` callback declares, in the order it declared them.
53
- *
54
- * Collected rather than dispatched to the parent as they are made: the chaining methods are
55
- * synchronous by contract, so a builder that executes could only fire and forget, which returned from
56
- * `alterTable` with the statements still in flight and turned a failure into an unhandled rejection.
57
- */
41
+ /** The operations one `alterTable` callback declares, collected in order, since its methods are synchronous. */
58
42
  class AlterTableBuilder {
59
43
  tableName;
60
44
  operations = [];
@@ -1,15 +1,2 @@
1
- /**
2
- * Splits a SQL blob on semicolons into separate statements.
3
- *
4
- * Uses a declarative Master-Regex scanner to rapidly
5
- * identify and skip "non-splittable" blocks (strings, comments, dollar-quotes) in a single pass.
6
- * This approach is $O(n)$, leverages native regex speed, and is trivially easy to audit.
7
- *
8
- * Handles:
9
- * - Single quotes: '...' (standard SQL, respects '' and \')
10
- * - Double quotes: "..." (identifiers or MySQL strings)
11
- * - Backticks: `...` (MySQL identifiers)
12
- * - Postgres Dollar quotes: $tag$...$tag$ or $$...$$
13
- * - Comments: -- and /* ... *\/
14
- */
1
+ /** Splits SQL on semicolons, skipping strings, quoted identifiers, dollar-quoted blocks and comments in one regex pass. */
15
2
  export declare function splitSqlStatements(sql: string): string[];
@@ -1,28 +1,8 @@
1
- /**
2
- * Splits a SQL blob on semicolons into separate statements.
3
- *
4
- * Uses a declarative Master-Regex scanner to rapidly
5
- * identify and skip "non-splittable" blocks (strings, comments, dollar-quotes) in a single pass.
6
- * This approach is $O(n)$, leverages native regex speed, and is trivially easy to audit.
7
- *
8
- * Handles:
9
- * - Single quotes: '...' (standard SQL, respects '' and \')
10
- * - Double quotes: "..." (identifiers or MySQL strings)
11
- * - Backticks: `...` (MySQL identifiers)
12
- * - Postgres Dollar quotes: $tag$...$tag$ or $$...$$
13
- * - Comments: -- and /* ... *\/
14
- */
1
+ /** Splits SQL on semicolons, skipping strings, quoted identifiers, dollar-quoted blocks and comments in one regex pass. */
15
2
  export function splitSqlStatements(sql) {
16
3
  const statements = [];
17
4
  let lastIndex = 0;
18
- // This regex matches:
19
- // 1. Single quoted strings: '(?:''|\\['\\]|[^'])*(?:'|(?=$))
20
- // 2. Double quoted identifiers: "(?:""|\\["\\]|[^"])*(?:"|(?=$))
21
- // 3. Backticked identifiers: `(?:``|\\[`\\]|[^`])*(?:`|(?=$))
22
- // 4. Postgres Dollar quoted blocks: \$(?<tag>[a-zA-Z0-9_]*)\$[\s\S]*?(?:\$\k<tag>|(?=$))
23
- // 5. Single-line comments: --.*
24
- // 6. Multi-line comments: \/\*[\s\S]*?(?:\*\/|(?=$))
25
- // 7. Statement terminator: ;
5
+ // Quoted strings and identifiers, dollar quotes, comments, or a `;`: the one regex scanning them all.
26
6
  const masterRegex = /'(?:''|\\['\\]|[^'])*(?:'|(?=$))|"(?:""|\\["\\]|[^"])*(?:"|(?=$))|`(?:``|\\[`\\]|[^`])*(?:`|(?=$))|\$(?<tag>[a-zA-Z0-9_]*)\$[\s\S]*?(?:\$\k<tag>|(?=$))|--.*|\/\*[\s\S]*?(?:\*\/|(?=$))|;/g;
27
7
  for (const match of sql.matchAll(masterRegex)) {
28
8
  if (match[0] === ';') {
@@ -67,14 +67,7 @@ export interface VectorColumnOptions extends BaseColumnOptions {
67
67
  /** Number of dimensions */
68
68
  dimensions?: number;
69
69
  }
70
- /**
71
- * A column as the builder describes one: {@link ColumnNode} without the graph links a DTO cannot carry.
72
- *
73
- * Derived rather than restated, so the two cannot drift. A column gained `enum` and this shape was
74
- * simply missing it, which is why a hand-written `createTable` could never constrain one. A field
75
- * added to the node now reaches here, and failing to render it is a compile error rather than a
76
- * column that quietly loses half its declaration.
77
- */
70
+ /** A column as the builder describes one: a {@link ColumnNode} without its graph links. */
78
71
  export type ColumnDefinition = Omit<ColumnNode, 'table' | 'referencedBy' | 'references'>;
79
72
  /**
80
73
  * The foreign key a single column declares: {@link ForeignKeySchema} without the local columns, which
@@ -260,13 +253,7 @@ export interface IForeignKeyBuilder extends IColumnBuilder {
260
253
  /** Set ON UPDATE action */
261
254
  onUpdate(action: ForeignKeyAction): this;
262
255
  }
263
- /**
264
- * The column vocabulary: every column type the builder can declare, name first.
265
- *
266
- * Split out of {@link ITableBuilder} so `addColumn`/`alterColumn` can hand it to their callback. They
267
- * used to take an {@link IColumnBuilder}, which has no way to say what type a column is - so the
268
- * builder hard-coded `VARCHAR`, and every column a generated migration added or altered was a string.
269
- */
256
+ /** Every column type the builder can declare, name first, handed to `addColumn`/`alterColumn` callbacks too. */
270
257
  export interface IColumnFactory {
271
258
  /** Add an auto-incrementing primary key */
272
259
  id(name?: string, options?: BaseColumnOptions): IColumnBuilder;
@@ -2,17 +2,8 @@ import { stat } from 'node:fs/promises';
2
2
  import { resolve } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  /**
5
- * Loads the config with a plain `import()`, leaving TypeScript to whatever runs the CLI.
6
- *
7
- * @remarks uql deliberately bundles no transpiler. The config imports the entity classes, so whoever
8
- * loads it decides which decorator spec their decorators are invoked with, and only the runtime knows
9
- * the project's `tsconfig.json`. Bun and `node --import tsx` both get it right; a bundled loader would
10
- * be guessing, and `jiti` guessed wrong (it hardcodes the legacy transform, so standard decorators were
11
- * called as `(prototype, key)` and every field was silently dropped).
12
- *
13
- * Node's own type stripping covers a config that is only types plus a plain object, which is why the
14
- * error below distinguishes the two cases: decorators are not erasable syntax, so a config that reaches
15
- * decorated entity classes needs a runtime that actually transforms them.
5
+ * Loads the config with a plain `import()`, leaving TypeScript to the runtime: uql bundles no transpiler,
6
+ * since only the project knows its decorator spec. Node's type stripping handles no decorators.
16
7
  */
17
8
  async function importConfig(path) {
18
9
  const mod = (await import(pathToFileURL(path).href).catch((cause) => {
@@ -76,11 +76,7 @@ export async function main(args = process.argv.slice(2)) {
76
76
  printHelp();
77
77
  process.exit(1);
78
78
  }
79
- // Close the connection pool
80
- const pool = config.pool;
81
- if (pool.end) {
82
- await pool.end();
83
- }
79
+ await config.pool.end();
84
80
  }
85
81
  catch (error) {
86
82
  console.error('Error:', error.message);
@@ -202,8 +198,7 @@ export async function runSync(migrator, args, config) {
202
198
  const force = args.includes('--force');
203
199
  const safe = !args.includes('--unsafe');
204
200
  const options = { force, safe, drop: !safe };
205
- // Ahead of the warning as well as of the run: `--dry-run` means the same thing whatever else was
206
- // asked for, and it used to be ignored beside `--force`.
201
+ // Ahead of the warning and the run: `--dry-run` means the same whatever else was asked for, `--force` included.
207
202
  if (args.includes('--dry-run')) {
208
203
  const statements = await migrator.planSync(options);
209
204
  console.log(statements.length ? `\n${statements.join('\n')}` : '\nSchema is already in sync.');
@@ -1,14 +1,3 @@
1
- /**
2
- * Entity Code Generator
3
- *
4
- * Generates TypeScript entity files from SchemaAST.
5
- * Supports:
6
- * - ES Module syntax
7
- * - TypeScript types
8
- * - Relations with proper decorators
9
- * - Indexes
10
- * - JSDoc comments for sync-added fields
11
- */
12
1
  import type { SchemaAST } from '../../schema/schemaAST.js';
13
2
  /**
14
3
  * Options for entity code generation.
@@ -107,10 +96,6 @@ export declare class EntityCodeGenerator {
107
96
  * Build incoming relation (OneToMany where other tables have FK to this).
108
97
  */
109
98
  private buildIncomingRelation;
110
- /**
111
- * Get decorator name for relation type.
112
- */
113
- private getRelationDecoratorName;
114
99
  /**
115
100
  * Format type for description.
116
101
  */