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
@@ -172,10 +172,10 @@ export type EntityData<E> = Pick<E, FieldKey<E> | RelationKey<E>>;
172
172
  * accept `QueryRaw` or `JsonUpdateOp` (for JSON fields), which gives IDE autocomplete for
173
173
  * `$set`/`$push`/`$pull` keys via `Json<infer T>`.
174
174
  */
175
- export type UpdatePayload<E> = {
176
- [K in FieldKey<E>]?: UpdateFieldValue<E[K]>;
175
+ export type UpdatePayload<E, F extends keyof E = FieldKey<E>, R extends keyof E = RelationKey<E>> = {
176
+ [K in F]?: UpdateFieldValue<E[K]>;
177
177
  } & {
178
- [K in RelationKey<E>]?: E[K];
178
+ [K in R]?: E[K];
179
179
  };
180
180
  /**
181
181
  * Infers the field values of an entity
@@ -212,9 +212,7 @@ export type IdKey<E> = ([NamedIdKey<E>] extends [never] ? FieldKey<E> : NamedIdK
212
212
  */
213
213
  export type IdValue<E> = E[IdKey<E>];
214
214
  /** Every column of a key, which is how a composite row is named and what a `$where` reduces to. */
215
- type IdMap<E> = {
216
- [K in IdKey<E>]?: E[K];
217
- };
215
+ type IdMap<E> = Partial<Pick<E, IdKey<E>>>;
218
216
  /**
219
217
  * How a row is addressed by its primary key: the value for a single key, an object carrying every
220
218
  * key for a composite - which is also the `$where` map it reduces to, so both spellings are one type.
@@ -478,17 +476,25 @@ export type FieldOptionsFor<V> = (FieldOptions<NonNullable<V>> & {
478
476
  * The entity a relation field points at: `Company` for both `company?: Company` and
479
477
  * `companies?: Company[]`.
480
478
  */
481
- export type RelationTarget<V> = NonNullable<Unpacked<NonNullable<V>>>;
479
+ export type RelationTarget<V> = Extract<Unpacked<V>, object>;
482
480
  /**
483
481
  * {@link RelationOptions} for a relation field declared as `V`, with `entity` required and pinned to
484
482
  * `V`'s own type, and the cardinality restricted to the ones that field shape can hold. Together those
485
483
  * reject `@ManyToOne({ entity: () => Other })` on a `Company` field, and any to-many cardinality on a
486
- * field that is not an array. An array field additionally needs a {@link RelationJoin}.
484
+ * field that is not an array. A to-many additionally needs a {@link RelationJoin}.
485
+ *
486
+ * The join is required through the `cardinality` written rather than through `IsMany<V>`: a conditional
487
+ * member of the intersection leaves a `mappedBy` callback without a contextual type inside a generic
488
+ * call (`defineEntity`), where a union keyed on a property does not.
487
489
  */
488
- export type RelationOptionsFor<V> = Omit<RelationOptions<RelationTarget<V>>, 'entity' | 'cardinality'> & {
490
+ export type RelationOptionsFor<V, O = unknown> = Omit<RelationOptions<RelationTarget<V>, O>, 'entity' | 'cardinality'> & {
489
491
  readonly entity: EntityGetter<RelationTarget<V>>;
490
492
  readonly cardinality: IsMany<V> extends true ? '1m' | 'mm' : '11' | 'm1';
491
- } & (IsMany<V> extends true ? RelationJoin<RelationTarget<V>> : unknown);
493
+ } & (({
494
+ readonly cardinality: '1m' | 'mm';
495
+ } & RelationJoin<RelationTarget<V>, O>) | {
496
+ readonly cardinality: '11' | 'm1';
497
+ });
492
498
  /**
493
499
  * The method names of an entity, so hook registrations name a method that exists.
494
500
  */
@@ -504,9 +510,13 @@ export type MethodKey<E> = {
504
510
  * entity graph almost always has. Nothing about the standard decorator spec changes that; it only removed
505
511
  * the reflected `design:type` that used to make `entity` optional.
506
512
  */
507
- export type EntityGetter<E = any> = () => Type<E>;
513
+ export type EntityGetter<E = object> = () => Type<E>;
508
514
  export type CascadeType = 'persist' | 'delete';
509
- export type RelationOptions<E = any> = {
515
+ /**
516
+ * `E` is the relation's target and `O` the entity declaring it, whose fields `references` names on its
517
+ * `local` side; the relation decorators infer `O` from the class they sit on.
518
+ */
519
+ export type RelationOptions<E, O = unknown> = {
510
520
  entity: EntityGetter<E>;
511
521
  cardinality: RelationCardinality;
512
522
  readonly cascade?: boolean | CascadeType;
@@ -519,13 +529,24 @@ export type RelationOptions<E = any> = {
519
529
  */
520
530
  readonly onDelete?: ForeignKeyAction;
521
531
  readonly onUpdate?: ForeignKeyAction;
522
- mappedBy?: RelationMappedBy<E>;
532
+ /** The inverse side: the member of the target holding the foreign key or the owning relation, `(post) => post.author`. */
533
+ mappedBy?: (keys: KeyMap<E>) => Key<E>;
523
534
  /**
524
535
  * The pivot entity of a many-to-many. Unconstrained by `E`: a pivot holds foreign keys to both
525
536
  * sides and is not a relation value of the target, so nothing about it is derivable from `E`.
526
537
  */
527
538
  through?: EntityGetter;
528
- references?: RelationReferences;
539
+ /**
540
+ * The join columns where no convention fits: each pairs a field of the declaring entity with one of
541
+ * the target, `(order, customer) => [{ local: order.customerCode, foreign: customer.code }]`. A
542
+ * `through` relation takes none: its junction's columns follow the convention.
543
+ */
544
+ references?: (local: KeyMap<O>, foreign: KeyMap<E>) => readonly RelationReference<O, E>[];
545
+ };
546
+ /** One pair of join columns, each a field read off its entity's key map. */
547
+ export type RelationReference<O, E> = {
548
+ readonly local: FieldKey<O>;
549
+ readonly foreign: FieldKey<E>;
529
550
  };
530
551
  /**
531
552
  * A relation once `getMeta` has resolved it: `references` is filled in and `mappedBy` is the key its
@@ -538,46 +559,48 @@ export type RelationOptions<E = any> = {
538
559
  * from "declared, but an inverse side too, so neither owns the foreign key" needs the unresolved shape
539
560
  * still there to find. A phase-split metadata map costs more than the call parentheses it saves.
540
561
  */
541
- export type RelationMeta<E = any> = Omit<RelationOptions<E>, 'mappedBy' | 'references'> & {
542
- mappedBy?: Key<E>;
562
+ export type RelationMeta = RelationRegistration & {
543
563
  references: RelationReferences;
544
564
  };
545
- /** How a to-many owner reaches its children: a junction entity, or the join columns by name. */
546
- type RelationOwnerJoin<E> = Required<Pick<RelationOptions<E>, 'through'>> | Required<Pick<RelationOptions<E>, 'references'>>;
565
+ /**
566
+ * A relation as the registry takes it, whichever entity it targets: `mappedBy` and `references` read
567
+ * off their key maps down to the names they give, `references` unset until `getMeta` settles it.
568
+ */
569
+ export type RelationRegistration = Omit<RelationOptions<object>, 'mappedBy' | 'references'> & {
570
+ mappedBy?: string;
571
+ references?: RelationReferences;
572
+ };
573
+ /** How a to-many owner reaches its children: a junction entity or the join columns, never both. */
574
+ type RelationOwnerJoin<E, O> = (Required<Pick<RelationOptions<E, O>, 'through'>> & {
575
+ readonly references?: never;
576
+ }) | (Required<Pick<RelationOptions<E, O>, 'references'>> & {
577
+ readonly through?: never;
578
+ });
547
579
  /**
548
580
  * Every way a to-many can say where its rows are. Required because nothing about the field implies it:
549
581
  * without one of the three, resolution has no columns to join on and throws.
550
582
  */
551
- type RelationJoin<E> = RelationOwnerJoin<E> | Required<Pick<RelationOptions<E>, 'mappedBy'>>;
552
- type RelationOptionsOwner<E> = Pick<RelationOptions<E>, 'entity' | 'references' | 'cascade' | 'onDelete' | 'onUpdate'>;
583
+ type RelationJoin<E, O> = RelationOwnerJoin<E, O> | Required<Pick<RelationOptions<E>, 'mappedBy'>>;
584
+ type RelationOptionsOwner<E, O> = Pick<RelationOptions<E, O>, 'entity' | 'references' | 'cascade' | 'onDelete' | 'onUpdate'>;
553
585
  type RelationOptionsInverseSide<E> = Pick<RelationOptions<E>, 'entity' | 'cascade'> & Required<Pick<RelationOptions<E>, 'mappedBy'>>;
554
- type RelationOptionsThroughOwner<E> = Pick<RelationOptions<E>, 'entity' | 'cascade'> & RelationOwnerJoin<E>;
586
+ type RelationOptionsThroughOwner<E, O> = Pick<RelationOptions<E, O>, 'entity' | 'cascade'> & RelationOwnerJoin<E, O>;
555
587
  /**
556
- * The key names of `E` as values, so `mappedBy` can be written as `(user) => user.company` instead of
557
- * a string literal and survive a rename.
558
- *
559
- * Mapping over `Key<E>` rather than `keyof E` is what makes the callback usable: a homomorphic
560
- * `[K in keyof E]` inherits the entity's optional modifiers, so `user.company` is
561
- * `'company' | undefined` and {@link RelationKeyMapper} rejects it - every callback needed a `!`.
562
- *
563
- * At runtime a callback only ever reads one property off the map, so a single `Proxy` returning its
564
- * own key stands in for every entity's: see `RELATION_KEY_MAP`. A key that names neither a field nor
565
- * a relation of the target is rejected when the entity resolves.
588
+ * The key names of `E` as values, so a definition reads a member off it - `(post) => post.author` -
589
+ * and follows a rename. Homomorphic in `E`, which is what keeps that link, and `-?` so an optional
590
+ * member still names itself. At runtime one `Proxy` answering its own key serves every entity.
566
591
  */
567
- export type RelationKeyMap<E> = {
568
- readonly [K in Key<E>]: K;
592
+ export type KeyMap<E> = {
593
+ readonly [K in keyof E]-?: K;
569
594
  };
570
- export type RelationKeyMapper<E> = (keyMap: RelationKeyMap<E>) => Key<E>;
571
595
  export type RelationReferences = {
572
596
  readonly local: string;
573
597
  readonly foreign: string;
574
598
  }[];
575
- export type RelationMappedBy<E> = Key<E> | RelationKeyMapper<E>;
576
599
  export type RelationCardinality = '11' | 'm1' | '1m' | 'mm';
577
- export type RelationOneToOneOptions<E> = RelationOptionsOwner<E> | RelationOptionsInverseSide<E>;
578
- export type RelationOneToManyOptions<E> = RelationOptionsInverseSide<E> | RelationOptionsThroughOwner<E>;
579
- export type RelationManyToOneOptions<E> = RelationOptionsOwner<E>;
580
- export type RelationManyToManyOptions<E> = RelationOptionsThroughOwner<E> | RelationOptionsInverseSide<E>;
600
+ export type RelationOneToOneOptions<E, O = unknown> = RelationOptionsOwner<E, O> | RelationOptionsInverseSide<E>;
601
+ export type RelationOneToManyOptions<E, O = unknown> = RelationOptionsInverseSide<E> | RelationOptionsThroughOwner<E, O>;
602
+ export type RelationManyToOneOptions<E, O = unknown> = RelationOptionsOwner<E, O>;
603
+ export type RelationManyToManyOptions<E, O = unknown> = RelationOptionsThroughOwner<E, O> | RelationOptionsInverseSide<E>;
581
604
  /**
582
605
  * Lifecycle hook event names.
583
606
  */
@@ -614,10 +637,10 @@ export type IndexTypeOptions = {
614
637
  *
615
638
  * @example
616
639
  * ```ts
617
- * @Index(['tenantId', { column: 'createdAt', order: 'desc' }]) // keyset pagination
618
- * @Index([raw`lower("email")`], { unique: true }) // case-insensitive uniqueness
619
- * @Index([{ column: 'body', length: 64 }]) // MySQL needs a prefix on TEXT
620
- * @Index(['data'], { type: 'gin' }) // JSONB containment
640
+ * @Index((post) => [post.tenantId, { column: post.createdAt, order: 'desc' }]) // keyset pagination
641
+ * @Index(() => [raw`lower("email")`], { unique: true }) // case-insensitive uniqueness
642
+ * @Index((post) => [{ column: post.body, length: 64 }]) // MySQL needs a prefix on TEXT
643
+ * @Index((post) => [post.data], { type: 'gin' }) // JSONB containment
621
644
  * ```
622
645
  *
623
646
  * `C` is the entity's `FieldKey` on the `@Index`/`defineEntity` paths, where the decorated class says
@@ -705,8 +728,8 @@ export type IndexColumnModifiers = {
705
728
  *
706
729
  * @example
707
730
  * ```ts
708
- * @Index([{ column: 'kind', jsonPath: { path: 'theme.color', type: String } }]) // 'kind.theme.color': 'red'
709
- * @Index([{ column: 'kind', jsonPath: { path: 'rating', type: Number } }]) // 'kind.rating': { $gte: 4 }
731
+ * @Index((user) => [{ column: user.kind, jsonPath: { path: 'theme.color', type: String } }]) // 'kind.theme.color': 'red'
732
+ * @Index((user) => [{ column: user.kind, jsonPath: { path: 'rating', type: Number } }]) // 'kind.rating': { $gte: 4 }
710
733
  * ```
711
734
  */
712
735
  export type IndexJsonPath = {
@@ -725,8 +748,8 @@ export type IndexJsonPath = {
725
748
  *
726
749
  * @example
727
750
  * ```ts
728
- * @Index([{ column: 'tags', jsonArray: { type: String, length: 64 } }]) // tags: { $all: [...] }
729
- * @Index([{ column: 'kind', jsonArray: { path: 'ids', type: Number } }]) // 'kind.ids': { $all: [...] }
751
+ * @Index((user) => [{ column: user.tags, jsonArray: { type: String, length: 64 } }]) // tags: { $all: [...] }
752
+ * @Index((user) => [{ column: user.kind, jsonArray: { path: 'ids', type: Number } }]) // 'kind.ids': { $all: [...] }
730
753
  * ```
731
754
  */
732
755
  export type IndexJsonArray = {
@@ -742,6 +765,8 @@ type IndexColumnPlainModifiers = Except<IndexColumnModifiers, 'jsonPath' | 'json
742
765
  export type IndexColumnOptions<C extends string = string> = IndexColumnPlainModifiers & {
743
766
  /** The column to index, or `raw(...)` for an expression. */
744
767
  readonly column: C | QueryRaw;
768
+ readonly jsonPath?: never;
769
+ readonly jsonArray?: never;
745
770
  };
746
771
  /**
747
772
  * One index entry, normalized: {@link IndexColumnInput}'s three authored shapes all reduce to this
@@ -830,9 +855,21 @@ export type CheckOptions = {
830
855
  */
831
856
  export type EntityMembers = {
832
857
  readonly fields?: Readonly<Record<string, FieldOptions | undefined>>;
833
- readonly relations?: Readonly<Record<string, RelationOptions | undefined>>;
858
+ readonly relations?: Readonly<Record<string, RelationRegistration | undefined>>;
834
859
  readonly hooks?: Readonly<Partial<Record<HookEvent, readonly string[]>>>;
835
860
  };
861
+ /** An entity's fields as `defineEntity` takes them, keyed like every entity map (see `QuerySelect`). */
862
+ type EntityFieldOptions<E, F extends keyof E = FieldKey<E>> = {
863
+ readonly [K in F]?: FieldOptionsFor<E[K]>;
864
+ };
865
+ /**
866
+ * An entity's relations as `defineEntity` takes them. Keyed over every member rather than `RelationKey<E>`:
867
+ * inside the generic call, only a map over `keyof E` gives a `mappedBy` callback its contextual type. A
868
+ * field named here still fails, on its options, since its value is no entity.
869
+ */
870
+ type EntityRelationOptions<E> = {
871
+ readonly [K in keyof E]?: RelationOptionsFor<E[K], E>;
872
+ };
836
873
  /**
837
874
  * Configurable options for an entity (`@Entity()` / `defineEntity`).
838
875
  *
@@ -849,38 +886,41 @@ export type EntityOptions<E = unknown> = {
849
886
  /** Named, default-on `$where` filters (soft-delete is auto-registered from `@Field({ softDelete })`). */
850
887
  readonly filters?: Record<string, FilterOptions<E>>;
851
888
  /** Scalar fields; use `isId: true` on exactly one field for the primary key. */
852
- readonly fields?: {
853
- readonly [K in FieldKey<E>]?: FieldOptionsFor<E[K]>;
854
- };
855
- readonly relations?: {
856
- readonly [K in RelationKey<E>]?: RelationOptionsFor<E[K]>;
857
- };
858
- readonly indexes?: readonly EntityIndexInput<FieldKey<E>, E>[];
889
+ readonly fields?: EntityFieldOptions<E>;
890
+ readonly relations?: EntityRelationOptions<E>;
891
+ readonly indexes?: readonly EntityIndexInput<E>[];
859
892
  /** Table-level `CHECK` constraints. See {@link CheckOptions}. */
860
893
  readonly checks?: readonly CheckOptions[];
861
- /** Map hook events to method names on the entity class. */
862
- readonly hooks?: Partial<Record<HookEvent, readonly MethodKey<E>[]>>;
894
+ /** Each lifecycle event and the methods it runs, read off the key map: `{ beforeInsert: (post) => [post.stamp] }`. */
895
+ readonly hooks?: Partial<Record<HookEvent, (keys: KeyMap<E>) => readonly MethodKey<E>[]>>;
863
896
  };
864
897
  /**
865
- * Everything an index carries beyond its columns, shared by `@Index`, `defineEntity` and the
866
- * migration builder's `table.index(...)`. `Except` (not plain `Omit`) keeps `type`/`distance` a
867
- * discriminated pair: omitting `distance` on a vector index type is a compile error.
898
+ * Everything an index carries beyond its columns, as the migration builder's `table.index(...)` takes it,
899
+ * and through {@link EntityIndexOptions} `@Index` and `defineEntity`. `Except` (not plain `Omit`) keeps
900
+ * `type`/`distance` a discriminated pair: omitting `distance` on a vector index type is a compile error.
868
901
  */
869
- export type IndexOptions<E = unknown> = Except<EntityIndexMeta, 'columns' | 'include' | 'where'> & {
870
- /** Non-key columns stored in the index; a typo builds nothing, the server refusing the statement. */
871
- readonly include?: readonly IndexFieldKey<E>[];
902
+ export type IndexOptions = Except<EntityIndexMeta, 'columns' | 'include' | 'where'> & {
903
+ /** Non-key columns stored in the index, by column name; a typo builds nothing, the server refusing it. */
904
+ readonly include?: readonly string[];
872
905
  /**
873
906
  * Partial-index predicate. `raw` with no interpolation, like an index expression: this is DDL, so
874
907
  * there is no placeholder for a bound value. A bare string is the older spelling and still works.
875
908
  */
876
909
  readonly where?: string | QueryRaw;
877
910
  };
878
- /** A field of `E`, or any name where there is no entity to check it against - the migration builder. */
879
- type IndexFieldKey<E> = unknown extends E ? string : FieldKey<E>;
880
911
  /**
881
- * An index as authored, before `defineIndex` normalizes its columns.
912
+ * {@link IndexOptions} on an entity, whose stored columns are read off its key map, `(post) => [post.slug]`,
913
+ * so they are checked against it and follow a rename. The migration builder names raw columns instead.
914
+ */
915
+ export type EntityIndexOptions<E> = Except<IndexOptions, 'include'> & {
916
+ readonly include?: (keys: KeyMap<E>) => readonly FieldKey<E>[];
917
+ };
918
+ /**
919
+ * An index as authored on an entity, before `defineIndex` reads its columns off the key map. Only the
920
+ * member lists are callbacks: TypeScript never checks a callback's returned literal for excess properties,
921
+ * so the options stay a literal of their own, where `uniqe: true` is a compile error.
882
922
  */
883
- export type EntityIndexInput<C extends string = string, E = unknown> = IndexOptions<E> & {
884
- readonly columns: readonly IndexColumnInput<C, E>[];
923
+ export type EntityIndexInput<E> = EntityIndexOptions<E> & {
924
+ readonly columns: (keys: KeyMap<E>) => readonly IndexColumnInput<FieldKey<E>, E>[];
885
925
  };
886
926
  export {};
@@ -73,7 +73,7 @@ export interface MigratorOptions {
73
73
  /**
74
74
  * Entities to use for schema generation
75
75
  */
76
- readonly entities?: Type<unknown>[];
76
+ readonly entities?: Type<object>[];
77
77
  /**
78
78
  * Default action for foreign key ON DELETE and ON UPDATE clauses.
79
79
  */
@@ -141,7 +141,7 @@ export interface IndexSchema extends VectorIndexOptions {
141
141
  /**
142
142
  * What the index is over, in order. Named `entries` and not `columns` because an entry need not be
143
143
  * a column at all: ``raw`lower(email)` `` is one, and so is a column carrying a prefix length or a
144
- * stored order. The authored form, `@Index([...])`, still spells this `columns`, since that is what
144
+ * stored order. The authored form, `@Index`, still spells this `columns`, since that is what
145
145
  * it reads like at the call site.
146
146
  */
147
147
  readonly entries: readonly IndexColumnSchema[];
@@ -234,7 +234,7 @@ export interface SyncOptions {
234
234
  readonly drop?: boolean;
235
235
  readonly logging?: boolean;
236
236
  /** One entity instead of every registered one, for a schema that grows while the process runs. */
237
- readonly entity?: Type<unknown>;
237
+ readonly entity?: Type<object>;
238
238
  /** Drop every table and recreate it. Development only: it is the one option that loses data. */
239
239
  readonly force?: boolean;
240
240
  }
@@ -266,13 +266,13 @@ export interface SchemaGenerator {
266
266
  * cross-entity foreign key has nothing to resolve against and is dropped: all three call sites that
267
267
  * used to work that way emitted schemas with no referential integrity.
268
268
  */
269
- generateCreateSchema(entities: readonly Type<unknown>[], options?: CreateSchemaOptions): string[];
269
+ generateCreateSchema(entities: readonly Type<object>[], options?: CreateSchemaOptions): string[];
270
270
  /**
271
271
  * Every `DROP TABLE` for `entities`, dependents first. The inverse of {@link generateCreateSchema},
272
272
  * and the reason it takes the whole set: dropping in any order that ignores the relation graph is
273
273
  * rejected once the foreign keys are really there.
274
274
  */
275
- generateDropSchema(entities: readonly Type<unknown>[], options?: DropSchemaOptions): string[];
275
+ generateDropSchema(entities: readonly Type<object>[], options?: DropSchemaOptions): string[];
276
276
  /** Generate DROP TABLE statement. */
277
277
  generateDropTable(tableName: string, options?: DropSchemaOptions): string;
278
278
  /**
@@ -303,7 +303,7 @@ export interface SchemaGenerator {
303
303
  * reads as missing from both sides, which is a match and no statement. Defaults to this entity
304
304
  * alone, which is right only where it has no relations.
305
305
  */
306
- diffSchema<E>(entity: Type<E>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
306
+ diffSchema(entity: Type<object>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
307
307
  /**
308
308
  * The entity side as an AST, to hand to every {@link diffSchema} of one run - building it per
309
309
  * entity instead is quadratic in the number of entities.
@@ -311,7 +311,7 @@ export interface SchemaGenerator {
311
311
  * Optional because not every generator compares one: MongoDB has no foreign keys and diffs only
312
312
  * indexes, so it neither implements this nor reads the argument.
313
313
  */
314
- buildAST?(entities: readonly Type<unknown>[]): SchemaAST;
314
+ buildAST?(entities: readonly Type<object>[]): SchemaAST;
315
315
  /**
316
316
  * The table's key: {@link resolveTableAlias} behind {@link resolveSchema}, which is how a
317
317
  * `SchemaAST` stores it and how a diff finds it again.
@@ -76,9 +76,9 @@ export interface QuerierPool<Q extends Querier = Querier, D extends AbstractDial
76
76
  export interface SqlQuerierPool<Q extends SqlQuerier = SqlQuerier, D extends AbstractSqlDialect = AbstractSqlDialect> extends QuerierPool<Q, D>, Pick<SqlQuerier, 'all' | 'run'> {
77
77
  }
78
78
  /** Dialect class used by pool `P` (when `P` is a {@link QuerierPool}). */
79
- export type QuerierPoolDialect<P> = P extends QuerierPool<any, infer D> ? D : never;
79
+ export type QuerierPoolDialect<P> = P extends QuerierPool<infer _Q, infer D> ? D : never;
80
80
  /** Querier type produced by pool `P`. */
81
- export type QuerierPoolQuerier<P> = P extends QuerierPool<infer Q, any> ? Q : never;
81
+ export type QuerierPoolQuerier<P> = P extends QuerierPool<infer Q, infer _D> ? Q : never;
82
82
  /**
83
83
  * Represents a high-compatibility SQL pool shim for Node.js integrations (e.g., express-session).
84
84
  */
@@ -26,12 +26,12 @@ export type QueryOptions = {
26
26
  autoPrefix?: boolean;
27
27
  };
28
28
  /**
29
- * Query field selection - `{ name: true }` whitelists specific fields. Fields only: a relation is a
30
- * sub-query rather than a projection flag, and a whitelist naming one could not say whether the
31
- * scalars come with it. Relations go in `$populate`.
29
+ * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over
30
+ * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its
31
+ * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.
32
32
  */
33
- export type QuerySelect<E> = {
34
- [K in FieldKey<E>]?: BooleanLike;
33
+ export type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {
34
+ [K in F]?: V;
35
35
  };
36
36
  /**
37
37
  * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`
@@ -46,8 +46,8 @@ export type QueryExclude<E> = QuerySelect<E>;
46
46
  /**
47
47
  * relation population map.
48
48
  */
49
- export type QueryPopulate<E> = {
50
- [K in RelationKey<E>]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;
49
+ export type QueryPopulate<E, R extends keyof E = RelationKey<E>> = {
50
+ [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;
51
51
  };
52
52
  /**
53
53
  * The key a read carries its relation tallies under. One spelling for the type and the runtime that
@@ -59,15 +59,13 @@ export declare const COUNT_RESULT_KEY = "_count";
59
59
  * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes
60
60
  * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.
61
61
  */
62
- export type QueryCount<E> = {
63
- [K in ToManyRelationKey<E>]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;
62
+ export type QueryCount<E, R extends keyof E = ToManyRelationKey<E>> = {
63
+ [K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;
64
64
  };
65
65
  /**
66
66
  * query conflict paths - subset of field keys used to detect upsert conflicts.
67
67
  */
68
- export type QueryConflictPaths<E> = {
69
- [K in FieldKey<E>]?: true;
70
- };
68
+ export type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;
71
69
  /**
72
70
  * Options to populate a relation declared as `V`, by its cardinality.
73
71
  */
@@ -149,9 +147,11 @@ export type QuerySortByCount = {
149
147
  * against an intersection is repeated per constituent, which made this the single most expensive
150
148
  * type in the package to check.
151
149
  */
152
- export type QuerySortMap<E, Vector extends boolean = true> = {
153
- [K in FieldKey<E> | JsonFieldPaths<E> | RelationKey<E>]?: K extends RelationKey<E> ? IsMany<E[K]> extends true ? QuerySortByCount : QuerySortMap<RelationTarget<E[K]>, false> : K extends FieldKey<E> ? Vector extends true ? NonNullable<E[K]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection : QuerySortDirection;
154
- };
150
+ export type QuerySortMap<E, Vector extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {
151
+ [P in K]?: P extends RelationKey<E> ? IsMany<E[P]> extends true ? QuerySortByCount : QuerySortMap<RelationTarget<E[P]>, false> : Vector extends true ? NonNullable<E[P]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection;
152
+ } & ([JsonFieldPaths<E>] extends [never] ? unknown : {
153
+ [P in JsonFieldPaths<E>]?: QuerySortDirection;
154
+ });
155
155
  /**
156
156
  * pager options.
157
157
  */
@@ -314,18 +314,10 @@ export type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$popula
314
314
  * @internal
315
315
  */
316
316
  type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> = {
317
- $select?: {
318
- [K in S]?: V;
319
- } | readonly QueryRaw[];
320
- $exclude?: {
321
- [K in X]?: V;
322
- };
323
- $populate?: {
324
- [K in P]?: QueryPopulate<E>[K];
325
- };
326
- $count?: {
327
- [K in C & keyof QueryCount<E>]?: QueryCount<E>[K];
328
- };
317
+ $select?: QuerySelect<E, S, V> | readonly QueryRaw[];
318
+ $exclude?: QuerySelect<E, X, V>;
319
+ $populate?: QueryPopulate<E, P>;
320
+ $count?: QueryCount<E, C & ToManyRelationKey<E>>;
329
321
  };
330
322
  /**
331
323
  * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.
@@ -1,9 +1,9 @@
1
1
  import type { FieldKey } from './entity.js';
2
- import type { QueryPager, QuerySortDirection } from './query.js';
2
+ import type { QueryPager, QuerySelect, QuerySortDirection } from './query.js';
3
3
  import type { QueryWhere, QueryWhereFieldValue } from './queryWhere.js';
4
4
  /**
5
5
  * Maps the offending keys to `never`, turning an excess key into a compile error; resolves to
6
- * `unknown` (an inert intersection member) when there are none. Needed because `$group`/`$agg` are
6
+ * `unknown` (an inert intersection member) when there are none. Needed because `$group`/`$select` are
7
7
  * captured as whole maps, and TypeScript skips excess-property checking on a naked type parameter.
8
8
  * A find captures key sets instead, where an unknown key fails the capture's own constraint.
9
9
  * @internal
@@ -21,7 +21,7 @@ type GroupedKeys<G> = {
21
21
  }[keyof G];
22
22
  /**
23
23
  * The keys `T` declares by name, or `never` when `T` is only an index signature - which is what an
24
- * uninferred `$agg` is, and what would otherwise make every key look like a declared alias.
24
+ * uninferred `$select` is, and what would otherwise make every key look like a declared alias.
25
25
  * @internal
26
26
  */
27
27
  type NamedKeys<T> = string extends keyof T ? never : keyof T;
@@ -57,8 +57,20 @@ export declare function resolveAggregateOp(key: string): {
57
57
  op: QueryAggregateOp;
58
58
  distinct: boolean;
59
59
  };
60
+ /**
61
+ * Exactly one key of `T` with its value; every other key is forbidden (`never`). `Pick`, not `Record`,
62
+ * so the chosen key stays linked to `T`'s own property and renames follow it through.
63
+ */
64
+ type ExactlyOne<T> = {
65
+ [K in keyof T]: Readonly<Pick<T, K>> & Partial<Readonly<Record<Exclude<keyof T, K>, never>>>;
66
+ }[keyof T];
67
+ /**
68
+ * One field named as a key - `{ amount: true }` - the way a statement names every field, so an editor
69
+ * rename reaches it where a string never would. `F` narrows which fields qualify.
70
+ */
71
+ export type QueryFieldRef<E, F extends keyof E = FieldKey<E>> = ExactlyOne<Required<QuerySelect<E, F, true>>>;
60
72
  /** The argument of an aggregate function: a field, or `'*'` (only meaningful for `COUNT(*)`). */
61
- export type QueryAggregateArg<E> = FieldKey<E> | '*';
73
+ export type QueryAggregateArg<E> = QueryFieldRef<E> | '*';
62
74
  /**
63
75
  * Fields `SUM`/`AVG` can total. Restricted to numeric columns because the result is declared
64
76
  * `number`: totalling a text or date column is either an engine error or a coercion, and neither
@@ -81,21 +93,17 @@ type TotallingOp = OpsOf<'$sum' | '$avg' | '$sumDistinct' | '$avgDistinct'>;
81
93
  * Every aggregate op mapped to the argument it accepts: `$count` a field or `'*'` (`COUNT(*)`),
82
94
  * the totalling ops a numeric field, `$min`/`$max`/`$countDistinct` any field.
83
95
  */
84
- type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, NumericFieldKey<E>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, FieldKey<E>>;
85
- /** Exactly one key of `T`: the chosen op with its value; every other op key is forbidden (`never`). */
86
- type ExactlyOne<T> = {
87
- [K in keyof T]: Readonly<Record<K, T[K]>> & Partial<Readonly<Record<Exclude<keyof T, K>, never>>>;
88
- }[keyof T];
96
+ type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, QueryFieldRef<E, NumericFieldKey<E>>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, QueryFieldRef<E>>;
89
97
  /**
90
98
  * An aggregate function applied to a field. Exactly one operation per entry (a second op is a
91
99
  * compile error). Only `$count` accepts `'*'` (i.e. `COUNT(*)`); every other op requires a field.
92
100
  * DISTINCT variants are flat ops (`$countDistinct`/`$sumDistinct`/`$avgDistinct`) taking a field.
93
101
  *
94
- * @example { $count: '*' } -> COUNT(*)
95
- * @example { $countDistinct: 'id' } -> COUNT(DISTINCT "id")
96
- * @example { $sum: 'amount' } -> SUM("amount")
97
- * @example { $sumDistinct: 'amount' } -> SUM(DISTINCT "amount")
98
- * @example { $avg: 'age' } -> AVG("age")
102
+ * @example { $count: '*' } -> COUNT(*)
103
+ * @example { $countDistinct: { id: true } } -> COUNT(DISTINCT "id")
104
+ * @example { $sum: { amount: true } } -> SUM("amount")
105
+ * @example { $sumDistinct: { amount: true } } -> SUM(DISTINCT "amount")
106
+ * @example { $avg: { age: true } } -> AVG("age")
99
107
  */
100
108
  export type QueryAggregateFn<E> = ExactlyOne<QueryAggregateArgMap<E>>;
101
109
  /** A single-key `{ [op]: unknown }` shape for each op in `Ops`, matched to infer that op's result. */
@@ -109,16 +117,14 @@ type CountingOp = OpsOf<'$count' | '$countDistinct'>;
109
117
  /**
110
118
  * Group-by columns: an object mapping entity field keys to `true`, exactly like {@link QuerySelect}.
111
119
  * Typed against the entity, so a typo'd column is a compile error. Compute aggregate columns with
112
- * {@link QueryAggMap} (the `$agg` key), not here.
120
+ * {@link QueryAggMap} (the `$select` key), not here.
113
121
  *
114
122
  * @example
115
123
  * ```ts
116
124
  * { status: true } // -> GROUP BY "status"
117
125
  * ```
118
126
  */
119
- export type QueryGroupMap<E> = {
120
- readonly [K in FieldKey<E>]?: true;
121
- };
127
+ export type QueryGroupMap<E> = Readonly<QuerySelect<E, FieldKey<E>, true>>;
122
128
  /**
123
129
  * Computed aggregate columns: an object mapping your chosen output alias to an aggregate function.
124
130
  * Alias names are free (you are naming new columns); the aggregated field reference inside each
@@ -126,7 +132,7 @@ export type QueryGroupMap<E> = {
126
132
  *
127
133
  * @example
128
134
  * ```ts
129
- * { count: { $count: '*' }, avgAge: { $avg: 'age' } }
135
+ * { count: { $count: '*' }, avgAge: { $avg: { age: true } } }
130
136
  * // -> COUNT(*) AS "count", AVG("age") AS "avgAge"
131
137
  * ```
132
138
  */
@@ -151,7 +157,7 @@ type QueryAggregateFnResult<E, Fn> = Fn extends FnWithOp<CountingOp> ? number :
151
157
  readonly $min: infer F;
152
158
  } | {
153
159
  readonly $max: infer F;
154
- } ? FieldValueType<E, F> | null : unknown;
160
+ } ? FieldValueType<E, keyof F> | null : unknown;
155
161
  /**
156
162
  * Flattens an intersection into a single object literal for readable editor hovers.
157
163
  * @internal
@@ -166,9 +172,7 @@ type Simplify<T> = {
166
172
  * Grouped columns come from {@link GroupedKeys}, not `keyof G`, so a `$group` the compiler could
167
173
  * not read contributes none rather than all of them.
168
174
  */
169
- export type QueryAggregateResult<E, G, A> = Simplify<{
170
- -readonly [K in GroupedKeys<G> & FieldKey<E>]: E[K];
171
- } & {
175
+ export type QueryAggregateResult<E, G, A> = Simplify<Pick<E, GroupedKeys<G> & FieldKey<E>> & {
172
176
  -readonly [K in keyof A]: QueryAggregateFnResult<E, A[K]>;
173
177
  }>;
174
178
  /**
@@ -190,7 +194,7 @@ export type QueryHavingMap = {
190
194
  * querier.aggregate(User, {
191
195
  * $where: { deletedAt: { $isNull: true } },
192
196
  * $group: { status: true },
193
- * $agg: { count: { $count: '*' }, avgAge: { $avg: 'age' } },
197
+ * $select: { count: { $count: '*' }, avgAge: { $avg: { age: true } } },
194
198
  * $having: { count: { $gt: 5 } },
195
199
  * $sort: { count: -1 },
196
200
  * });
@@ -203,17 +207,22 @@ export type QueryAggregate<E, G extends QueryGroupMap<E> = QueryGroupMap<E>, A e
203
207
  readonly $where?: QueryWhere<E>;
204
208
  /**
205
209
  * Columns to group by - `{ status: true }`, typed against the entity like `$select`. A computed
206
- * aggregate wrongly placed here (it belongs in `$agg`) is rejected via {@link Reject}, since
207
- * `$group` is captured as a generic and a bare generic skips excess-property checking.
210
+ * aggregate wrongly placed here (it belongs in `$select`) is rejected via {@link Reject}, since
211
+ * `$group` is captured as a generic and a bare generic skips excess-property checking. The captured
212
+ * map meets its schema, {@link QueryGroupMap}, so each key keeps its link to the entity property.
208
213
  */
209
- readonly $group?: G & Reject<Exclude<keyof G, FieldKey<E>>>;
214
+ readonly $group?: G & QueryGroupMap<E> & Reject<Exclude<keyof G, FieldKey<E>>>;
210
215
  /**
211
- * Computed aggregate columns - `{ count: { $count: '*' }, avgAge: { $avg: 'age' } }`.
216
+ * Computed aggregate columns - `{ count: { $count: '*' }, avgAge: { $avg: { age: true } } }`. The
217
+ * captured map meets its schema over the same aliases, as `$group` does, so field keys stay linked
218
+ * (a `Record<keyof A, ...>` spelling of the same type breaks the inference of `A`).
212
219
  *
213
220
  * An alias repeating a `$group` column is rejected: both would be emitted under that one name,
214
221
  * leaving the driver to keep whichever it read last.
215
222
  */
216
- readonly $agg?: A & Reject<NamedKeys<A> & GroupedKeys<G>>;
223
+ readonly $select?: A & {
224
+ readonly [K in keyof A]: QueryAggregateFn<E>;
225
+ } & Reject<NamedKeys<A> & GroupedKeys<G>>;
217
226
  /**
218
227
  * Post-aggregation filtering, applied after grouping (SQL `HAVING`, MongoDB post-group `$match`).
219
228
  * Keyed by the result columns (grouped columns + computed aliases), and each value is typed to that