uql-orm 0.57.0 → 0.58.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 (44) hide show
  1. package/README.md +6 -8
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +3 -3
  4. package/dist/dialect/abstractSqlDialect.d.ts +2 -2
  5. package/dist/dialect/abstractSqlDialect.js +4 -4
  6. package/dist/dialect/mysqlLikeSqlDialect.d.ts +1 -1
  7. package/dist/dialect/mysqlLikeSqlDialect.js +1 -1
  8. package/dist/dialect/queryJoins.js +1 -0
  9. package/dist/entity/decorator/bag.d.ts +2 -2
  10. package/dist/entity/decorator/entity.d.ts +8 -9
  11. package/dist/entity/decorator/entity.js +6 -7
  12. package/dist/entity/decorator/members.d.ts +7 -6
  13. package/dist/entity/decorator/members.js +2 -1
  14. package/dist/entity/metadata/definition.d.ts +16 -11
  15. package/dist/entity/metadata/definition.js +51 -39
  16. package/dist/http/handler.d.ts +2 -2
  17. package/dist/http/handler.js +0 -1
  18. package/dist/migrate/codegen/entityCodeGenerator.js +6 -4
  19. package/dist/migrate/codegen/entityTypes.d.ts +1 -1
  20. package/dist/migrate/codegen/entityTypes.js +4 -3
  21. package/dist/migrate/codegen/indexDecoratorSource.d.ts +5 -4
  22. package/dist/migrate/codegen/indexDecoratorSource.js +17 -13
  23. package/dist/migrate/codegen/sourceLiteral.d.ts +2 -0
  24. package/dist/migrate/codegen/sourceLiteral.js +4 -0
  25. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +3 -3
  26. package/dist/migrate/migrator.d.ts +2 -2
  27. package/dist/migrate/schemaGenerator.d.ts +5 -5
  28. package/dist/mongo/mongoDialect.d.ts +1 -1
  29. package/dist/mongo/mongoDialect.js +3 -3
  30. package/dist/mongo/mongodbQuerier.js +0 -1
  31. package/dist/querier/abstractSqlQuerier.js +6 -6
  32. package/dist/schema/schemaASTBuilder.d.ts +3 -3
  33. package/dist/schema/schemaASTBuilder.js +2 -2
  34. package/dist/type/config.d.ts +1 -1
  35. package/dist/type/entity.d.ts +108 -68
  36. package/dist/type/migration.d.ts +7 -7
  37. package/dist/type/querierPool.d.ts +2 -2
  38. package/dist/type/query.d.ts +19 -27
  39. package/dist/type/queryAggregate.d.ts +38 -29
  40. package/dist/type/queryWhere.d.ts +12 -9
  41. package/dist/util/dialect.util.d.ts +3 -3
  42. package/dist/util/dialect.util.js +24 -15
  43. package/dist/util/relationQuery.util.d.ts +1 -1
  44. package/package.json +1 -1
@@ -38,18 +38,41 @@ export function defineField(entity, key, opts = {}) {
38
38
  export function defineId(entity, key, opts) {
39
39
  return defineField(entity, key, { ...opts, isId: true });
40
40
  }
41
- // `RelationOptions` is parameterized by the *target* entity, which is independent of the owner `E`, so it
42
- // is left at its default here rather than tied to the class being registered.
41
+ /** `T` is the relation's target, independent of the owner `E`. */
43
42
  export function defineRelation(entity, key, opts) {
44
- if (!opts.entity) {
43
+ return addRelation(entity, key, relationRegistration(opts));
44
+ }
45
+ /**
46
+ * `opts` as the registry takes them: `mappedBy` and `references` read off their key maps down to the
47
+ * names they give. The callbacks only read properties, so they run here, before any entity has to
48
+ * exist, and the registry holds data alone.
49
+ */
50
+ export function relationRegistration({ mappedBy, references, ...opts }) {
51
+ return {
52
+ ...opts,
53
+ ...(mappedBy ? { mappedBy: mappedBy(keyMap()) } : {}),
54
+ ...(references ? { references: [...references(keyMap(), keyMap())] } : {}),
55
+ };
56
+ }
57
+ /** Every entity's key map: a callback only reads one property off it, and that property is its own key. */
58
+ function keyMap() {
59
+ return KEY_MAP;
60
+ }
61
+ const KEY_MAP = new Proxy({}, { get: (_, key) => key });
62
+ function addRelation(entity, key, registration) {
63
+ if (!registration.entity) {
45
64
  throw new TypeError(`'${entity.name}.${key}' needs an 'entity' getter, e.g. '@ManyToOne({ entity: () => Company })'.`);
46
65
  }
66
+ if (registration.through && registration.references) {
67
+ throw new TypeError(`'${entity.name}.${key}' joins through a junction, whose columns follow the convention; 'references' ` +
68
+ "pairs the declaring entity's columns with the target's instead.");
69
+ }
47
70
  const meta = ensureWritableMeta(entity);
48
- // Registration writes the authored shape into a map declared as resolved: `getMeta` runs
49
- // `fillRelations`, which settles `entity`, `references` and `mappedBy` or throws. Bridging the two
50
- // shapes here is what lets every consumer read `RelationMeta` without asserting.
71
+ // Registration writes into a map declared as resolved: `getMeta` runs `fillRelations`, which settles
72
+ // `references` or throws. Bridging the two shapes here is what lets every consumer read `RelationMeta`
73
+ // without asserting.
51
74
  const relations = meta.relations;
52
- relations[key] = { ...relations[key], ...opts };
75
+ relations[key] = { ...relations[key], ...registration };
53
76
  return meta;
54
77
  }
55
78
  export function defineHook(entity, methodName, event) {
@@ -62,18 +85,18 @@ export function defineHook(entity, methodName, event) {
62
85
  return meta;
63
86
  }
64
87
  /**
65
- * Declares a composite index. `unique` and the authored column sugar are normalized here, which is what
66
- * lets the dialects render one shape instead of re-parsing it.
88
+ * Declares a composite index, its columns read off the key map. `unique` and the authored column sugar
89
+ * are normalized here, which is what lets the dialects render one shape instead of re-parsing it.
67
90
  */
68
91
  export function defineIndex(entity, index) {
69
92
  const meta = ensureWritableMeta(entity);
70
- if (!meta.indexes)
71
- meta.indexes = [];
72
- meta.indexes.push({
93
+ const keys = keyMap();
94
+ (meta.indexes ??= []).push({
73
95
  ...index,
74
96
  unique: index.unique ?? false,
75
97
  where: ddlText(index.where, 'a partial-index predicate'),
76
- columns: index.columns.map(normalizeIndexColumn),
98
+ columns: index.columns(keys).map(normalizeIndexColumn),
99
+ include: index.include?.(keys),
77
100
  });
78
101
  return meta;
79
102
  }
@@ -104,7 +127,7 @@ export function applyMembers(entity, specs) {
104
127
  }
105
128
  }
106
129
  for (const [key, spec] of definedEntries(specs?.relations ?? {})) {
107
- defineRelation(entity, key, spec);
130
+ addRelation(entity, key, spec);
108
131
  }
109
132
  for (const [event, methodNames] of definedEntries(specs?.hooks ?? {})) {
110
133
  for (const methodName of methodNames) {
@@ -113,10 +136,8 @@ export function applyMembers(entity, specs) {
113
136
  }
114
137
  }
115
138
  /**
116
- * Registers an entity described by data alone, minting the class the registry keys it by. The row
117
- * type follows from the spec - see {@link SpecRow} - so a definition written out is checked column by
118
- * column, and one assembled at runtime is the column bag it is. Pass `Row` to name a shape the spec
119
- * cannot describe, such as the interface `uql-migrate types` generated for it.
139
+ * Registers a class as an entity from `opts` alone, the decorator-free counterpart of `@Entity()` with
140
+ * `@Field`/`@ManyToOne`/...
120
141
  */
121
142
  export function defineEntity(entity, opts = {}) {
122
143
  // Ahead of any registration, so a rejected definition leaves nothing half-written in the registry.
@@ -132,7 +153,12 @@ export function defineEntity(entity, opts = {}) {
132
153
  // drains `context.metadata` itself, because TypeScript only attaches `Symbol.metadata` to the class
133
154
  // after class decorators return; draining empties the bag, so whichever runs second is a no-op.
134
155
  applyMembers(entity, ownRegistrations(entity));
135
- applyMembers(entity, opts);
156
+ const keys = keyMap();
157
+ applyMembers(entity, {
158
+ fields: opts.fields,
159
+ relations: Object.fromEntries(definedEntries(opts.relations ?? {}).map(([key, spec]) => [key, relationRegistration(spec)])),
160
+ hooks: Object.fromEntries(definedEntries(opts.hooks ?? {}).map(([event, methods]) => [event, methods(keys)])),
161
+ });
136
162
  // Unnamed checks are named by the generator, as unnamed indexes are.
137
163
  for (const check of opts.checks ?? []) {
138
164
  (meta.checks ??= []).push({ name: check.name, expression: ddlText(check.expression, 'a check constraint') });
@@ -267,12 +293,7 @@ export function removeEntity(entity) {
267
293
  return metas.delete(entity);
268
294
  }
269
295
  export function getEntities() {
270
- return metas.entries().reduce((acc, [key, val]) => {
271
- if (val.ids.length) {
272
- acc.push(key);
273
- }
274
- return acc;
275
- }, []);
296
+ return [...metas.values()].filter((meta) => meta.ids.length).map((meta) => meta.entity);
276
297
  }
277
298
  /**
278
299
  * The metadata of `entity`, marked as changed. Every `define*` goes through this, and nothing outside
@@ -308,11 +329,11 @@ export function getMeta(entity) {
308
329
  }
309
330
  function fillRelations(meta) {
310
331
  for (const [relKey, relation] of definedEntries(meta.relations)) {
311
- // The authored view: `mappedBy` may still be the callback and `references` unset until this settles them.
332
+ // The registered view: `references` may be unset until this settles it.
312
333
  const relOpts = relation;
313
334
  const at = `'${meta.entity.name}.${relKey}'`;
314
335
  if (relOpts.mappedBy) {
315
- fillInverseSide(at, meta, relOpts);
336
+ fillInverseSide(at, meta, relOpts, relOpts.mappedBy);
316
337
  }
317
338
  else if (!relOpts.references) {
318
339
  fillOwningSide(at, meta, relKey, relOpts);
@@ -326,8 +347,8 @@ function fillRelations(meta) {
326
347
  for (const { local } of relOpts.references) {
327
348
  if (junction.fields[local])
328
349
  continue;
329
- throw new TypeError(`${at} joins through '${junction.entity.name}', which has no '${local}' field. Declare it, or name ` +
330
- "the join columns with 'references'.");
350
+ throw new TypeError(`${at} joins through '${junction.entity.name}', which has no '${local}' field: a junction's ` +
351
+ 'columns are named after the entities it joins. Declare it.');
331
352
  }
332
353
  }
333
354
  }
@@ -372,11 +393,9 @@ function fillOwningSide(at, meta, relKey, relOpts) {
372
393
  };
373
394
  }
374
395
  }
375
- function fillInverseSide(at, meta, relOpts) {
396
+ function fillInverseSide(at, meta, relOpts, mappedBy) {
376
397
  const relEntity = relOpts.entity();
377
398
  const relMeta = getMeta(relEntity);
378
- const mappedBy = getMappedByKey(relOpts);
379
- relOpts.mappedBy = mappedBy;
380
399
  if (relOpts.references)
381
400
  return;
382
401
  if (relMeta.fields[mappedBy]) {
@@ -448,13 +467,6 @@ function fillForeignKeyRelations(meta) {
448
467
  function junctionColumn(meta, idKey) {
449
468
  return lowerFirst(entityName(meta)) + upperFirst(fieldOf(meta, idKey).name ?? idKey);
450
469
  }
451
- /** A callback only reads one property off the key map, and that property is the key, so one serves every entity. */
452
- const RELATION_KEY_MAP = new Proxy({}, { get: (_, key) => key });
453
- function getMappedByKey(relOpts) {
454
- return typeof relOpts.mappedBy === 'function'
455
- ? relOpts.mappedBy(RELATION_KEY_MAP)
456
- : relOpts.mappedBy;
457
- }
458
470
  /** Every key the entity marks, in declaration order. More than one is a composite primary key. */
459
471
  function getIdKeys(meta) {
460
472
  return getKeys(meta.fields).filter((key) => meta.fields[key]?.isId);
@@ -49,8 +49,8 @@ export type HookContext<E extends object, Ctx = unknown> = {
49
49
  export type Hook<Ctx = unknown> = <E extends object>(ctx: HookContext<E, Ctx>) => void | Promise<void>;
50
50
  export type ResponseHook<Ctx = unknown> = <E extends object>(ctx: HookContext<E, Ctx>, envelope: RequestSuccessResponse<unknown>) => void | Promise<void>;
51
51
  export type RequestHandlerOptions<Ctx = unknown> = {
52
- include?: Type<any>[];
53
- exclude?: Type<any>[];
52
+ include?: Type<object>[];
53
+ exclude?: Type<object>[];
54
54
  /**
55
55
  * The URL segment an entity is addressed by, defaulting to its kebab-cased class name.
56
56
  *
@@ -27,7 +27,6 @@ export function createRequestHandler(opts) {
27
27
  "A route is the kebab-cased class name unless 'entityPath' says otherwise. Name them apart, " +
28
28
  "pass an 'entityPath', or pass only one of them in 'include'.");
29
29
  }
30
- // oxlint-disable-next-line typescript/no-explicit-any -- heterogeneous entity map
31
30
  const entityByPath = new Map([...byPath].map(([path, [entity]]) => [path, entity]));
32
31
  return (req) => {
33
32
  const entity = entityByPath.get(req.entityPath);
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { canonicalToTypeScript } from '../../schema/canonicalType.js';
13
13
  import { DEFAULT_FOREIGN_KEY_ACTION, } from '../../schema/types.js';
14
- import { camelCase, pascalCase, singularize } from '../../util/string.util.js';
14
+ import { camelCase, lowerFirst, pascalCase, singularize } from '../../util/string.util.js';
15
15
  import { buildFieldOptionsSource, fieldNeedsRaw } from './fieldOptionsSource.js';
16
16
  import { buildIndexDecoratorSource, indexNeedsRaw, isPlainFieldIndex } from './indexDecoratorSource.js';
17
17
  /**
@@ -116,8 +116,9 @@ export class EntityCodeGenerator {
116
116
  buildEntityDecorators(table) {
117
117
  const lines = [];
118
118
  if (this.options.includeIndexes) {
119
+ const param = lowerFirst(this.options.classNameTransformer(table.name));
119
120
  for (const index of this.declaredIndexes(table)) {
120
- lines.push(buildIndexDecoratorSource(index, this.options.propertyNameTransformer));
121
+ lines.push(buildIndexDecoratorSource(index, this.options.propertyNameTransformer, param));
121
122
  }
122
123
  }
123
124
  // Entity decorator
@@ -260,9 +261,10 @@ export class EntityCodeGenerator {
260
261
  lines.push(` * Inverse relation from ${rel.from.table.name}`);
261
262
  lines.push(' */');
262
263
  }
263
- // Decorator - includes references to property name
264
+ // The inverse side, mapped by the related class's property that points back at this one.
264
265
  const inverseProp = this.options.propertyNameTransformer(this.options.singularize(table.name));
265
- lines.push(` @${decoratorName}({ entity: () => ${relatedClassName}, references: '${inverseProp}' })`);
266
+ const param = lowerFirst(relatedClassName);
267
+ lines.push(` @${decoratorName}({ entity: () => ${relatedClassName}, mappedBy: (${param}) => ${param}.${inverseProp} })`);
266
268
  // Property
267
269
  if (inverseType === 'OneToMany' || inverseType === 'ManyToMany') {
268
270
  lines.push(` ${propertyName}?: ${relatedClassName}[];`);
@@ -4,4 +4,4 @@ import type { Type } from '../../type/index.js';
4
4
  * compiler too. Interfaces, not the entity classes `generate:from-db` writes: those are the source of
5
5
  * a schema, these describe one already defined elsewhere.
6
6
  */
7
- export declare function entityTypesSource(entities: readonly Type<unknown>[]): string;
7
+ export declare function entityTypesSource(entities: readonly Type<object>[]): string;
@@ -2,6 +2,7 @@ import { getMeta } from '../../entity/index.js';
2
2
  import { canonicalToTypeScript } from '../../schema/canonicalType.js';
3
3
  import { resolveColumnCanonicalType } from '../../schema/schemaASTBuilder.js';
4
4
  import { isToManyRelation, upperFirst } from '../../util/index.js';
5
+ import { isIdentifierName } from './sourceLiteral.js';
5
6
  /**
6
7
  * A `.d.ts` for the entities as registered, so a shape that only exists at runtime reaches the
7
8
  * compiler too. Interfaces, not the entity classes `generate:from-db` writes: those are the source of
@@ -41,12 +42,12 @@ function interfaceNames(metas) {
41
42
  }
42
43
  /** `text` as an identifier: what cannot be in one is dropped, and what cannot start one is prefixed. */
43
44
  function identifier(text) {
44
- const stripped = text.replace(/[^A-Za-z0-9_$]/g, '');
45
- return /^[A-Za-z_$]/.test(stripped) ? stripped : `Entity${stripped}`;
45
+ const stripped = text.replace(/[^\p{ID_Continue}$\u200C\u200D]/gu, '');
46
+ return isIdentifierName(stripped) ? stripped : `Entity${stripped}`;
46
47
  }
47
48
  /** A column name a property cannot hold - `hero-image` - is quoted rather than dropped. */
48
49
  function member(key, type) {
49
- const name = key === identifier(key) ? key : JSON.stringify(key);
50
+ const name = isIdentifierName(key) ? key : JSON.stringify(key);
50
51
  return ` ${name}?: ${type};`;
51
52
  }
52
53
  /**
@@ -2,13 +2,14 @@ import type { IndexNode } from '../../schema/types.js';
2
2
  /**
3
3
  * Whether `@Field({ index })` can carry the whole index. It says only "this column is indexed under
4
4
  * this name", so anything else the index declares - an expression, a predicate, uniqueness, an access
5
- * method, stored columns, a stored order - has to be written out as `@Index([...])` instead.
5
+ * method, stored columns, a stored order - has to be written out as an `@Index` instead.
6
6
  */
7
7
  export declare function isPlainFieldIndex(index: IndexNode): boolean;
8
8
  /**
9
- * One `@Index([...])` as source, for an index no `@Field` can express. Emits `raw(...)` for an
10
- * expression entry, so callers import `raw` when {@link indexNeedsRaw} holds.
9
+ * One `@Index((user) => [...])` as source, for an index no `@Field` can express, its columns read off the
10
+ * key map `param` names. Emits `raw(...)` for an expression entry, so callers import `raw` when
11
+ * {@link indexNeedsRaw} holds.
11
12
  */
12
- export declare function buildIndexDecoratorSource(index: IndexNode, propertyName: (column: string) => string): string;
13
+ export declare function buildIndexDecoratorSource(index: IndexNode, propertyName: (column: string) => string, param: string): string;
13
14
  /** Whether emitting this index needs `raw` imported alongside `Index`. */
14
15
  export declare function indexNeedsRaw(index: IndexNode): boolean;
@@ -1,4 +1,4 @@
1
- import { rawTag } from './sourceLiteral.js';
1
+ import { isIdentifierName, quoted, rawTag } from './sourceLiteral.js';
2
2
  /**
3
3
  * A vector index carries its metric in the operator class pgvector names after it
4
4
  * (`vector_cosine_ops`), which is the only place introspection can recover it from. `@Index` requires
@@ -37,7 +37,7 @@ function significantModifiers(entry) {
37
37
  /**
38
38
  * Whether `@Field({ index })` can carry the whole index. It says only "this column is indexed under
39
39
  * this name", so anything else the index declares - an expression, a predicate, uniqueness, an access
40
- * method, stored columns, a stored order - has to be written out as `@Index([...])` instead.
40
+ * method, stored columns, a stored order - has to be written out as an `@Index` instead.
41
41
  */
42
42
  export function isPlainFieldIndex(index) {
43
43
  const entries = index.entries;
@@ -53,11 +53,12 @@ export function isPlainFieldIndex(index) {
53
53
  significantModifiers(entry).length === 0);
54
54
  }
55
55
  /**
56
- * One `@Index([...])` as source, for an index no `@Field` can express. Emits `raw(...)` for an
57
- * expression entry, so callers import `raw` when {@link indexNeedsRaw} holds.
56
+ * One `@Index((user) => [...])` as source, for an index no `@Field` can express, its columns read off the
57
+ * key map `param` names. Emits `raw(...)` for an expression entry, so callers import `raw` when
58
+ * {@link indexNeedsRaw} holds.
58
59
  */
59
- export function buildIndexDecoratorSource(index, propertyName) {
60
- const entries = index.entries.map((entry) => indexEntrySource(entry, propertyName)).join(', ');
60
+ export function buildIndexDecoratorSource(index, propertyName, param) {
61
+ const entries = index.entries.map((entry) => indexEntrySource(entry, propertyName, param)).join(', ');
61
62
  const isVector = index.type === 'hnsw' || index.type === 'ivfflat';
62
63
  const distance = isVector ? vectorDistance(index) : undefined;
63
64
  const options = [];
@@ -77,21 +78,24 @@ export function buildIndexDecoratorSource(index, propertyName) {
77
78
  if (index.where)
78
79
  options.push(`where: ${rawTag(index.where)}`);
79
80
  if (index.include?.length) {
80
- options.push(`include: [${index.include.map((column) => `'${propertyName(column)}'`).join(', ')}]`);
81
+ const included = index.include.map((column) => memberSource(param, propertyName(column)));
82
+ options.push(`include: (${param}) => [${included.join(', ')}]`);
81
83
  }
82
- return `@Index([${entries}]${options.length > 0 ? `, { ${options.join(', ')} }` : ''})`;
84
+ return `@Index((${param}) => [${entries}]${options.length > 0 ? `, { ${options.join(', ')} }` : ''})`;
83
85
  }
84
86
  /** Whether emitting this index needs `raw` imported alongside `Index`. */
85
87
  export function indexNeedsRaw(index) {
86
88
  return Boolean(index.where) || index.entries.some((entry) => entry.expression);
87
89
  }
88
- function indexEntrySource(entry, propertyName) {
90
+ function indexEntrySource(entry, propertyName, param) {
89
91
  if (entry.expression) {
90
92
  return rawTag(entry.column);
91
93
  }
94
+ const column = memberSource(param, propertyName(entry.column));
92
95
  const modifiers = significantModifiers(entry);
93
- if (modifiers.length === 0) {
94
- return `'${propertyName(entry.column)}'`;
95
- }
96
- return `{ column: '${propertyName(entry.column)}', ${modifiers.join(', ')} }`;
96
+ return modifiers.length === 0 ? column : `{ column: ${column}, ${modifiers.join(', ')} }`;
97
+ }
98
+ /** `user.email`, or `user['first-name']` for a property name that is no identifier. */
99
+ function memberSource(param, property) {
100
+ return isIdentifierName(property) ? `${param}.${property}` : `${param}[${quoted(property)}]`;
97
101
  }
@@ -1,3 +1,5 @@
1
+ /** Whether `text` can name a property unquoted, in any script: `dueño` can, `first-name` cannot. */
2
+ export declare function isIdentifierName(text: string): boolean;
1
3
  /**
2
4
  * A string as single-quoted source. Introspected text is arbitrary - a comment or a default
3
5
  * expression can hold a quote or a backslash - and only escaping both keeps the generated file
@@ -1,3 +1,7 @@
1
+ /** Whether `text` can name a property unquoted, in any script: `dueño` can, `first-name` cannot. */
2
+ export function isIdentifierName(text) {
3
+ return /^[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*$/u.test(text);
4
+ }
1
5
  /**
2
6
  * A string as single-quoted source. Introspected text is arbitrary - a comment or a default
3
7
  * expression can hold a quote or a backslash - and only escaping both keeps the generated file
@@ -13,8 +13,8 @@ export declare class MongoSchemaGenerator extends AbstractDialect implements Sch
13
13
  * defer and no order to respect: this is each collection and nothing more. `foreignKeys` is accepted
14
14
  * and ignored for the same reason.
15
15
  */
16
- generateCreateSchema(entities: readonly Type<unknown>[], options?: CreateSchemaOptions): string[];
17
- generateDropSchema(entities: readonly Type<unknown>[]): string[];
16
+ generateCreateSchema(entities: readonly Type<object>[], options?: CreateSchemaOptions): string[];
17
+ generateDropSchema(entities: readonly Type<object>[]): string[];
18
18
  private selected;
19
19
  /**
20
20
  * The indexes `@Field({ index })` declares, as the collection would hold them.
@@ -48,5 +48,5 @@ export declare class MongoSchemaGenerator extends AbstractDialect implements Sch
48
48
  ifNotExists?: boolean;
49
49
  }): string[];
50
50
  generateRenameTableSql(oldName: string, newName: string): string;
51
- diffSchema<E>(entity: Type<E>, currentTable: TableNode | undefined): SchemaDiff | undefined;
51
+ diffSchema(entity: Type<object>, currentTable: TableNode | undefined): SchemaDiff | undefined;
52
52
  }
@@ -12,7 +12,7 @@ export declare class Migrator {
12
12
  get logger(): LoggerWrapper;
13
13
  set logger(value: LoggingOptions);
14
14
  private readonly _entities?;
15
- get entities(): Type<unknown>[];
15
+ get entities(): Type<object>[];
16
16
  readonly dialectName: DialectName;
17
17
  schemaGenerator?: SchemaGenerator;
18
18
  schemaIntrospector?: SchemaIntrospector;
@@ -89,7 +89,7 @@ export declare class Migrator {
89
89
  * {@link schemaIntrospector} so a caller that replaced it still wins.
90
90
  */
91
91
  private introspectClaimedSchemas;
92
- findEntityForTable(tableName: string): Promise<Type<unknown> | undefined>;
92
+ findEntityForTable(tableName: string): Promise<Type<object> | undefined>;
93
93
  /**
94
94
  * Applies the entity schema to the database: every registered entity, or the one `entity` names.
95
95
  *
@@ -46,7 +46,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
46
46
  protected columnSqlType(col: ColumnNode): string;
47
47
  protected canonicalTypeToSql(type: CanonicalType): string;
48
48
  /** The entity side as an AST, carrying this generator's default referential action. */
49
- buildAST(entities: readonly Type<unknown>[]): SchemaAST;
49
+ buildAST(entities: readonly Type<object>[]): SchemaAST;
50
50
  /**
51
51
  * Every `CREATE TABLE` for `entities`, then their foreign keys.
52
52
  *
@@ -56,14 +56,14 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
56
56
  * then `createForeignKeys()`). SQLite is the exception and keeps them inline: it cannot `ALTER` a
57
57
  * foreign key in, but it resolves targets lazily, so a forward reference is fine there.
58
58
  */
59
- generateCreateSchema(entities: readonly Type<unknown>[], options?: CreateSchemaOptions): string[];
59
+ generateCreateSchema(entities: readonly Type<object>[], options?: CreateSchemaOptions): string[];
60
60
  /**
61
61
  * One statement per distinct schema the tables being created live in, in first-seen order. Only
62
62
  * the tables actually being created, so a narrowed `only` does not declare namespaces it is not
63
63
  * about to fill. Empty on an engine without schemas, whose tables are never qualified.
64
64
  */
65
65
  private generateCreateSchemas;
66
- generateDropSchema(entities: readonly Type<unknown>[], options?: DropSchemaOptions): string[];
66
+ generateDropSchema(entities: readonly Type<object>[], options?: DropSchemaOptions): string[];
67
67
  /**
68
68
  * The tables of `entities` in dependency order, optionally narrowed to `only`. The AST always spans
69
69
  * every entity even when narrowed, so a relation pointing at a table outside the subset still
@@ -132,7 +132,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
132
132
  * longer disagree about what has changed. Only two things are this side's own: the entity becomes a
133
133
  * table node first, and types are compared as the *engine* would store them - see `normalizeType`.
134
134
  */
135
- diffSchema<E>(entity: Type<E>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
135
+ diffSchema(entity: Type<object>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
136
136
  /**
137
137
  * Indexes the entity declares that the table does not already have, in any shape.
138
138
  *
@@ -217,7 +217,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
217
217
  * other way and the table is created under one name and compared under another, which reports every
218
218
  * table of a project using a naming strategy as both missing and unexpected.
219
219
  */
220
- export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName'>, entities: readonly Type<unknown>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
220
+ export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
221
221
  /**
222
222
  * Synchronous factory for SQL schema generators only.
223
223
  * For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
@@ -152,7 +152,7 @@ export declare class MongoDialect extends AbstractDialect {
152
152
  /** Whether a `$sort` reads a relation, which is what forces the lookups to run before it. */
153
153
  sortsRelations<E extends Document>(entity: Type<E>, sort: QuerySortMap<E> | undefined): boolean;
154
154
  /**
155
- * Aggregate results are keyed by `$group`/`$agg` alias rather than by column, so an aggregate
155
+ * Aggregate results are keyed by `$group`/`$select` alias rather than by column, so an aggregate
156
156
  * `$sort` addresses those aliases as-is - the same reason the SQL dialects sort by alias there.
157
157
  */
158
158
  private aliasSort;
@@ -551,7 +551,7 @@ export class MongoDialect extends AbstractDialect {
551
551
  return someKey(sort, (key) => Boolean(meta.relations[key]));
552
552
  }
553
553
  /**
554
- * Aggregate results are keyed by `$group`/`$agg` alias rather than by column, so an aggregate
554
+ * Aggregate results are keyed by `$group`/`$select` alias rather than by column, so an aggregate
555
555
  * `$sort` addresses those aliases as-is - the same reason the SQL dialects sort by alias there.
556
556
  */
557
557
  aliasSort(sort) {
@@ -958,7 +958,7 @@ export class MongoDialect extends AbstractDialect {
958
958
  }
959
959
  }
960
960
  // $group stage
961
- const { groupId, groupAccumulators, distinctReducers } = this.buildGroupSpec(getMeta(entity), parseGroupMap(q.$group, q.$agg));
961
+ const { groupId, groupAccumulators, distinctReducers } = this.buildGroupSpec(getMeta(entity), parseGroupMap(q.$group, q.$select));
962
962
  pipeline.push({ $group: { _id: hasKeys(groupId) ? groupId : null, ...groupAccumulators } });
963
963
  // Project stage - rename _id fields back to their original names, and reduce collected distinct
964
964
  // sets. Needed whenever there are group keys OR any distinct alias.
@@ -1008,7 +1008,7 @@ export class MongoDialect extends AbstractDialect {
1008
1008
  */
1009
1009
  buildGroupSpec(meta, groupEntries) {
1010
1010
  if (!groupEntries.length) {
1011
- throw new TypeError('aggregate requires at least one $group column or $agg function');
1011
+ throw new TypeError('aggregate requires at least one $group column or $select function');
1012
1012
  }
1013
1013
  const groupId = {};
1014
1014
  const groupAccumulators = {};
@@ -126,7 +126,6 @@ export class MongodbQuerier extends AbstractQuerier {
126
126
  async internalAggregate(entity, q, opts) {
127
127
  return this.timed('internalAggregate', undefined, async () => {
128
128
  const pipeline = this.dialect.buildAggregateStages(entity, q, opts);
129
- // oxlint-disable-next-line typescript/no-explicit-any -- aggregate result type matches QueryAggregateResult at runtime but TS can't verify
130
129
  return this.execute((session) => this.collection(entity).aggregate(pipeline, { session }).toArray());
131
130
  });
132
131
  }
@@ -329,17 +329,17 @@ export class AbstractSqlQuerier extends AbstractQuerier {
329
329
  async internalAggregate(entity, q, opts) {
330
330
  const ctx = this.dialect.createContext();
331
331
  this.dialect.aggregate(ctx, entity, q, opts);
332
- // oxlint-disable-next-line typescript/no-explicit-any -- raw DB rows satisfy QueryAggregateResult at runtime but TS can't verify
333
- const res = await this.all(ctx.sql, ctx.values);
332
+ const rows = await this.all(ctx.sql, ctx.values);
334
333
  const hydratable = this.dialect.hydratableAggregates(entity, q);
335
- for (const row of res) {
334
+ for (const row of rows) {
335
+ const cells = row;
336
336
  for (const [alias, kind] of hydratable) {
337
- if (row[alias] != null) {
338
- row[alias] = decodeColumn(row[alias], kind);
337
+ if (cells[alias] != null) {
338
+ cells[alias] = decodeColumn(cells[alias], kind);
339
339
  }
340
340
  }
341
341
  }
342
- return res;
342
+ return rows;
343
343
  }
344
344
  async internalInsertMany(entity, rows) {
345
345
  const meta = getMeta(entity);
@@ -15,9 +15,9 @@ import { type CanonicalType, type ForeignKeyAction } from './types.js';
15
15
  */
16
16
  export interface BuildSchemaASTOptions {
17
17
  /** Custom resolver for a table's own name, unqualified. */
18
- resolveTableName?: (meta: EntityMeta<unknown>) => string;
18
+ resolveTableName?: (meta: EntityMeta<object>) => string;
19
19
  /** Custom resolver for the schema a table lives in; `undefined` leaves it unqualified. */
20
- resolveSchema?: (meta: EntityMeta<unknown>) => string | undefined;
20
+ resolveSchema?: (meta: EntityMeta<object>) => string | undefined;
21
21
  /** Custom column name resolver */
22
22
  resolveColumnName?: (key: string, field: FieldOptions) => string;
23
23
  /** Naming strategy to use */
@@ -31,7 +31,7 @@ export interface BuildSchemaASTOptions {
31
31
  * Three passes, because each needs the one before it to have finished for *every* entity: a relation
32
32
  * resolves against a table another entity declares, and an index against the columns of its own.
33
33
  */
34
- export declare function buildSchemaAST(entities: readonly Type<unknown>[], options?: BuildSchemaASTOptions): SchemaAST;
34
+ export declare function buildSchemaAST(entities: readonly Type<object>[], options?: BuildSchemaASTOptions): SchemaAST;
35
35
  /**
36
36
  * Resolve the canonical type for a field, inheriting from the referenced
37
37
  * entity's primary key when the field is a foreign-key reference
@@ -168,7 +168,7 @@ function addRelationshipsFromEntity(ctx, meta) {
168
168
  }
169
169
  }
170
170
  /**
171
- * Add indexes from field options (`@Field({ index })`), from `@Index([...])`, and for every foreign
171
+ * Add indexes from field options (`@Field({ index })`), from `@Index`, and for every foreign
172
172
  * key none of those already serves.
173
173
  */
174
174
  function addIndexesFromEntity(ctx, meta) {
@@ -230,7 +230,7 @@ function resolveIncludeColumn(ctx, meta, column) {
230
230
  return field ? ctx.resolveColumnName(column, field) : column;
231
231
  }
232
232
  /**
233
- * One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
233
+ * One `@Index`. Its entries keep the authored form (expression, prefix length, order) with
234
234
  * names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
235
235
  * subset, which is what diffing and introspection compare.
236
236
  */
@@ -17,7 +17,7 @@ export interface Config {
17
17
  * List of entity classes to be managed by the ORM.
18
18
  * If omitted, classes that completed `defineEntity` (including via `@Entity()`) are discovered via `getEntities()`.
19
19
  */
20
- entities?: Type<unknown>[];
20
+ entities?: Type<object>[];
21
21
  /**
22
22
  * The directory where migration files are stored.
23
23
  * @default './migrations'