objection 4.0.0-rc.2 → 4.0.0-rc.3

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.
@@ -18,7 +18,8 @@ import { Knex } from 'knex';
18
18
  // Only the declarations marked with `export` are exported.
19
19
  export {};
20
20
 
21
- // Phantom brand of the relation types, used by `TypedRelationMappings`.
21
+ // Phantom brand of the relation types, used by `TypedRelationMappings` and
22
+ // `FromModel`.
22
23
  declare const relationKind: unique symbol;
23
24
  // Phantom brand of `Generated` properties.
24
25
  declare const generated: unique symbol;
@@ -197,7 +198,7 @@ export interface CallbackVoid<T> {
197
198
  }
198
199
 
199
200
  export type Identity<T> = (value: T) => T;
200
- export type AnyQueryBuilder = QueryBuilder<any, any>;
201
+ export type AnyQueryBuilder = QueryBuilder<any, any, any>;
201
202
  export type AnyModelConstructor = ModelConstructor<Model>;
202
203
  export type ModifierFunction<QB extends AnyQueryBuilder> = (qb: QB, ...args: any[]) => void;
203
204
  export type Modifier<QB extends AnyQueryBuilder = AnyQueryBuilder> =
@@ -398,7 +399,7 @@ export type WithGraphTreeQueryBuilder<QB extends AnyQueryBuilder, T> = [keyof T]
398
399
  ? QB
399
400
  : WithResultKind<
400
401
  QB,
401
- WithGraph<ModelType<QB>, T>['QueryBuilderType'],
402
+ KeepExtra<WithGraph<ModelType<QB>, T>['QueryBuilderType'], ExtraType<QB>>,
402
403
  ModelType<QB>,
403
404
  ResultType<QB>,
404
405
  T
@@ -477,8 +478,9 @@ export type NonFunctionPropertyNames<T> = {
477
478
  [K in keyof T]: T[K] extends Function ? never : K;
478
479
  }[keyof T];
479
480
 
480
- // The type-only members of models, which aren't data.
481
- type ModelTypeMembers = 'QueryBuilderType' | 'ColumnNameMapping';
481
+ // The type-only members of models, which aren't data, also the keys of the
482
+ // virtual attributes that `FromModel` adds.
483
+ type ModelTypeMembers = 'QueryBuilderType' | 'ColumnNameMapping' | `~virtual:${string}`;
482
484
 
483
485
  /**
484
486
  * Type that attempts to only select the user-defined model properties.
@@ -501,15 +503,24 @@ export type ModelObject<T extends Model> = Pick<T, DataPropertyNames<T>>;
501
503
 
502
504
  /**
503
505
  * The JSON of model M, as `toJSON()` returns it: its properties without
504
- * methods, and the related models as JSON too. Values aren't converted, e.g.
505
- * dates stay dates.
506
+ * methods, the results of the methods in `virtualAttributes` on models with
507
+ * `FromModel`, and the related models as JSON too. Values aren't converted,
508
+ * e.g. dates stay dates.
506
509
  */
507
510
  export type ModelJson<M> = {
508
- [K in keyof Pick<M, DataPropertyNames<M> & keyof M>]: K extends RelationPropertyNames<M>
511
+ [
512
+ K in keyof Pick<M, (DataPropertyNames<M> | VirtualAttributeNames<M>) & keyof M>
513
+ ]: K extends RelationPropertyNames<M>
509
514
  ? RelationJson<M[K]>
510
- : M[K];
515
+ : M[K] extends (...args: any) => infer R
516
+ ? RelationJson<R>
517
+ : M[K];
511
518
  };
512
519
 
520
+ type VirtualAttributeNames<M> = {
521
+ [K in keyof M]: K extends `~virtual:${infer N}` ? N : never;
522
+ }[keyof M];
523
+
513
524
  type RelationJson<P> = P extends (infer I)[]
514
525
  ? ModelJson<I>[]
515
526
  : P extends readonly (infer I)[]
@@ -728,16 +739,34 @@ export type ModelType<T extends { ModelType: any }> = T['ModelType'];
728
739
  export type ResultType<T extends { ResultType: any }> = T['ResultType'];
729
740
 
730
741
  /**
731
- * Re-instantiates the query builder QB with the model type M and the result
732
- * type R, through its `RebindType`. The `~rebind` slot is `unknown`, so the
733
- * intersection resolves it to exactly `[M, R]`. Rebinding QB's own
734
- * `RebindType` instead of QB keeps the intersection small, which TypeScript
735
- * checks much faster.
742
+ * Re-instantiates the query builder QB with the model type M, the result
743
+ * type R and the extra type X, through its `RebindType`. The `~rebind` slot is
744
+ * `unknown`, so the intersection resolves it to exactly `[M, R, X]`.
745
+ * Rebinding QB's own `RebindType` instead of QB keeps the intersection small,
746
+ * which TypeScript checks much faster.
736
747
  */
737
- type Rebind<QB extends AnyQueryBuilder, M extends Model, R> = (QB['RebindType'] & {
738
- '~rebind': [M, R];
748
+ type Rebind<
749
+ QB extends AnyQueryBuilder,
750
+ M extends Model,
751
+ R,
752
+ X = IfStrict<unknown, ExtraType<QB>>,
753
+ > = (QB['RebindType'] & {
754
+ '~rebind': [M, R, X];
739
755
  })['RebindType'];
740
756
 
757
+ /**
758
+ * Gets the extra type from a query builder type: what the types know about
759
+ * the query beyond its model and result, see `QueryExtra`.
760
+ */
761
+ export type ExtraType<T extends { ExtraType: any }> = T['ExtraType'];
762
+
763
+ type WithExtra<QB extends AnyQueryBuilder, X> = Rebind<QB, ModelType<QB>, ResultType<QB>, X>;
764
+
765
+ // The narrowed query builders of `withGraph*()` start without the extra type
766
+ // of QB, so it is passed on, if there is one.
767
+ type KeepExtra<NQB extends AnyQueryBuilder, X> =
768
+ X extends QueryExtra<any, any, any> ? WithExtra<NQB, X> : NQB;
769
+
741
770
  /**
742
771
  * Gets the single item query builder type for a query builder.
743
772
  */
@@ -763,18 +792,18 @@ export type NumberQueryBuilder<QB extends AnyQueryBuilder> = QB['~number'];
763
792
  */
764
793
  export type PageQueryBuilder<QB extends AnyQueryBuilder> = QB['~page'];
765
794
 
766
- // Strict mode, on by default, checks the column names and values of queries
767
- // against the models. Augment `TypeConfig` to opt out:
795
+ // Strict mode checks the column names and values of queries against the
796
+ // models. Augment `TypeConfig` to opt in:
768
797
  //
769
798
  // declare module 'objection' {
770
799
  // interface TypeConfig {
771
- // strict: false;
800
+ // strict: true;
772
801
  // }
773
802
  // }
774
803
 
775
804
  export interface TypeConfig {}
776
805
 
777
- type IsStrict = TypeConfig extends { strict: false } ? false : true;
806
+ type IsStrict = TypeConfig extends { strict: true } ? true : false;
778
807
 
779
808
  // As IsStrict isn't generic, IfStrict resolves eagerly.
780
809
  type IfStrict<Loose, Strict> = IsStrict extends true ? Strict : Loose;
@@ -1159,6 +1188,18 @@ export type SelectedBrand<A> = { readonly [selected]: A };
1159
1188
  */
1160
1189
  export type ExtractedBrand<A> = { readonly [extracted]: A };
1161
1190
 
1191
+ /**
1192
+ * The extra type of queries in strict mode: the model class C that started the
1193
+ * query, e.g. with `Person.query()`, and the modifiers P registered with
1194
+ * `modifiers()`, to check the names of modifiers, and the id I of the class,
1195
+ * for `findById()` and the other `*ById()` methods.
1196
+ */
1197
+ export interface QueryExtra<C = unknown, P = unknown, I = MaybeCompositeId> {
1198
+ modelClass: C;
1199
+ registered: P;
1200
+ id: I;
1201
+ }
1202
+
1162
1203
  // The results whose rows have `jsonExtract()` values, which `withGraph*()`
1163
1204
  // keeps instead of converting the result kind:
1164
1205
  type ExtractedRows =
@@ -1276,17 +1317,222 @@ type UnknownRelations<M, T> = {
1276
1317
 
1277
1318
  /**
1278
1319
  * In strict mode, relation expressions are intersected with this, to reject
1279
- * the names of unknown relations.
1320
+ * the names of unknown relations, and of unknown modifiers, checked against
1321
+ * the related class of each node and the modifiers registered on query
1322
+ * builder QB.
1280
1323
  */
1281
- type CheckRelationExpression<M, E> = IfStrict<
1324
+ type CheckRelationExpression<M, E, QB extends AnyQueryBuilder = never> = IfStrict<
1282
1325
  unknown,
1283
1326
  IsConcreteModel<M> extends true
1284
1327
  ? [UnknownRelations<M, ParseRelationExpression<E>>] extends [never]
1285
- ? unknown
1328
+ ? CheckModifierExpression<E, QB>
1286
1329
  : { '~unknownRelation': UnknownRelations<M, ParseRelationExpression<E>> }
1287
1330
  : unknown
1288
1331
  >;
1289
1332
 
1333
+ type CheckModifierExpression<E, QB extends AnyQueryBuilder> =
1334
+ HasQueryClass<QB> extends true
1335
+ ? [CheckedModifiers<E, QB>] extends [never]
1336
+ ? unknown
1337
+ : { '~unknownModifier': CheckedModifiers<E, QB> }
1338
+ : unknown;
1339
+
1340
+ // Queries in generic code, where the model class can't be resolved, aren't
1341
+ // checked.
1342
+ type HasQueryClass<QB extends AnyQueryBuilder> =
1343
+ IsConcreteModel<ModelType<QB>> extends false
1344
+ ? false
1345
+ : [QueryClass<QB>] extends [never]
1346
+ ? false
1347
+ : true;
1348
+
1349
+ // Only expressions with modifiers are checked for them.
1350
+ type CheckedModifiers<E, QB> = E extends `${string}(${string}` | object
1351
+ ? UnknownModifiers<
1352
+ ClassMappings<QueryClass<QB>>,
1353
+ ParseModifierExpression<E>,
1354
+ keyof RegisteredModifiers<QB>
1355
+ >
1356
+ : never;
1357
+
1358
+ /**
1359
+ * The modifiers that a modifier name can refer to on query builder QB: the
1360
+ * `modifiers` of the model class that started the query, and those registered
1361
+ * with `modifiers()` before. Unknown names are only rejected in strict mode,
1362
+ * and only on queries of model classes with literal `modifiers`.
1363
+ */
1364
+ type KnownModifiers<QB> = ClassModifiers<QueryClass<QB>> & RegisteredModifiers<QB>;
1365
+
1366
+ // The model class that started the query, if any.
1367
+ type QueryClass<QB> = [QB] extends [never]
1368
+ ? never
1369
+ : QB extends { ExtraType: QueryExtra<infer C, any, any> }
1370
+ ? unknown extends C
1371
+ ? never
1372
+ : C
1373
+ : never;
1374
+
1375
+ type ClassModifiers<C> = C extends { modifiers: infer O } ? O : {};
1376
+
1377
+ type ClassMappings<C> = C extends { relationMappings: infer T }
1378
+ ? T extends () => infer R
1379
+ ? R
1380
+ : T
1381
+ : {};
1382
+
1383
+ type RegisteredModifiers<QB> = [QB] extends [never]
1384
+ ? {}
1385
+ : QB extends { ExtraType: { registered: infer P } }
1386
+ ? P
1387
+ : {};
1388
+
1389
+ /**
1390
+ * The id that `findById()` and the other `*ById()` methods take on query
1391
+ * builder QB: in strict mode, the type of the property of the class's literal
1392
+ * `idColumn`, or a tuple of them for composite ids, e.g. with
1393
+ * `static idColumn = ['groupId', 'memberId'] as const`.
1394
+ */
1395
+ type IdArg<QB extends AnyQueryBuilder> = IfStrict<
1396
+ MaybeCompositeId,
1397
+ [QueryClass<QB>] extends [never]
1398
+ ? MaybeCompositeId
1399
+ : QB extends { ExtraType: QueryExtra<any, any, infer I> }
1400
+ ? I
1401
+ : MaybeCompositeId
1402
+ >;
1403
+
1404
+ // The id of a related class, read from its instances.
1405
+ type ClassId<C> = C extends { idColumn: infer K; prototype: infer M }
1406
+ ? ColumnsId<K, M>
1407
+ : MaybeCompositeId;
1408
+
1409
+ // The inherited `idColumn` and other general types give any id.
1410
+ type ColumnsId<K, M> = [K] extends [string]
1411
+ ? string extends K
1412
+ ? MaybeCompositeId
1413
+ : PropertyId<M, K, MaybeCompositeId>
1414
+ : [K] extends [readonly string[]]
1415
+ ? string extends K[number]
1416
+ ? MaybeCompositeId
1417
+ : { readonly [I in keyof K]: PropertyId<M, K[I] & string, Id> }
1418
+ : MaybeCompositeId;
1419
+
1420
+ // The id column is a database column, named after the property by the
1421
+ // model's `ColumnNameMapping`. Columns without a property give the fallback F.
1422
+ type PropertyId<M, C extends string, F> =
1423
+ ResultKey<M, C> extends infer P extends keyof M ? NonNullable<M[P]> : F;
1424
+
1425
+ // Modifiers with their general type, e.g. inherited from `Model`, aren't
1426
+ // checked.
1427
+ type ChecksModifiers<QB extends AnyQueryBuilder> = IfStrict<
1428
+ false,
1429
+ HasQueryClass<QB> extends true ? ChecksClassModifiers<ClassModifiers<QueryClass<QB>>> : false
1430
+ >;
1431
+
1432
+ type ChecksClassModifiers<O> = string extends keyof O ? false : true;
1433
+
1434
+ // The names that `modify()` takes on query builder QB, also in lists. Names
1435
+ // in variables typed `string` are rejected where they are checked.
1436
+ type ModifierName<QB extends AnyQueryBuilder> =
1437
+ ChecksModifiers<QB> extends true ? keyof KnownModifiers<QB> & string : string;
1438
+
1439
+ type ModifyArg<QB extends AnyQueryBuilder> =
1440
+ | ModifierName<QB>
1441
+ | ModifierName<QB>[]
1442
+ | ModifierFunction<QB>
1443
+ | Record<string, Expression<PrimitiveValue>>;
1444
+
1445
+ // The related classes are found through the relation mappings, at each step
1446
+ // of the path. P are the names registered with `modifiers()`.
1447
+ type UnknownModifiers<Mappings, T, P> = T extends unknown
1448
+ ? {
1449
+ [K in keyof T & string]: T[K] extends { names: infer N; children: infer Children }
1450
+ ? MappedClass<Mappings, K> extends infer C
1451
+ ? [C] extends [never]
1452
+ ? never
1453
+ : | (ChecksClassModifiers<ClassModifiers<C>> extends true
1454
+ ? Exclude<N, keyof ClassModifiers<C> | P>
1455
+ : never)
1456
+ | UnknownModifiers<ClassMappings<C>, Children, P>
1457
+ : never
1458
+ : never;
1459
+ }[keyof T & string]
1460
+ : never;
1461
+
1462
+ // Mappings with their general type, e.g. inherited from `Model`, give no class.
1463
+ type MappedClass<Mappings, K> = string extends keyof Mappings
1464
+ ? never
1465
+ : K extends keyof Mappings
1466
+ ? Mappings[K] extends { modelClass: infer C }
1467
+ ? C extends abstract new (...args: any) => any
1468
+ ? C
1469
+ : C extends () => infer RC
1470
+ ? RC
1471
+ : never
1472
+ : never
1473
+ : never;
1474
+
1475
+ /**
1476
+ * Parses the modifiers of relation expressions into a tree of the relations
1477
+ * with their modifier names: 'pets(a, b).owner' ->
1478
+ * `{ pets: { names: 'a' | 'b', children: { owner: ... } } }`. It is separate
1479
+ * from `ParseRelationExpression`, so that the narrowing types don't carry the
1480
+ * names.
1481
+ */
1482
+ type ParseModifierExpression<E> = string extends E
1483
+ ? {}
1484
+ : E extends string
1485
+ ? ParseModifierString<E>
1486
+ : ParseModifierObject<E>;
1487
+
1488
+ // A union of the trees of the list items, as they can name the same relation.
1489
+ type ParseModifierList<L extends string[], Acc = never> = L extends [
1490
+ infer H extends string,
1491
+ ...infer T extends string[],
1492
+ ]
1493
+ ? ParseModifierList<T, Acc | ParseModifierString<H>>
1494
+ : Acc;
1495
+
1496
+ type ParseModifierString<S extends string> =
1497
+ Trim<S> extends `[${infer Inner}]`
1498
+ ? ParseModifierList<SplitList<Inner>>
1499
+ : Trim<S> extends `${infer Head}.${infer Rest}`
1500
+ ? IsBalanced<Head> extends true
1501
+ ? ModifierNode<Head, ParseModifierString<Rest>>
1502
+ : {}
1503
+ : ModifierNode<S, {}>;
1504
+
1505
+ type ModifierNode<S extends string, Children> = [RelationName<S>] extends [never]
1506
+ ? {}
1507
+ : { [K in RelationName<S>]: { names: ModifierNames<S>; children: Children } };
1508
+
1509
+ // Empty arguments, e.g. of 'pets()' or a trailing comma, are no names.
1510
+ type ModifierNames<S extends string> =
1511
+ Trim<S> extends `${string}(${infer Args})` ? Exclude<Trim<SplitList<Args>[number]>, ''> : never;
1512
+
1513
+ type ParseModifierObject<E> = string extends keyof E
1514
+ ? {}
1515
+ : {
1516
+ [
1517
+ K in keyof E as K extends `$${string}`
1518
+ ? never
1519
+ : false extends E[K]
1520
+ ? never
1521
+ : E[K] extends { $relation: any }
1522
+ ? never
1523
+ : K
1524
+ ]: {
1525
+ names: E[K] extends { $modify: infer N }
1526
+ ? N extends readonly (infer I extends string)[]
1527
+ ? I
1528
+ : N extends string
1529
+ ? N
1530
+ : never
1531
+ : never;
1532
+ children: E[K] extends object ? ParseModifierObject<E[K]> : {};
1533
+ };
1534
+ };
1535
+
1290
1536
  export interface ForClassMethod {
1291
1537
  <M extends Model>(modelClass: ModelConstructor<M>): QueryBuilderType<M>;
1292
1538
  }
@@ -1664,7 +1910,7 @@ export interface JoinRelatedOptions {
1664
1910
 
1665
1911
  export interface JoinRelatedMethod<out QB extends AnyQueryBuilder> {
1666
1912
  <QBP extends QB, const E extends RelationExpression<ModelType<QBP>> = never>(
1667
- expr: E & CheckRelationExpression<ModelType<QBP>, E>,
1913
+ expr: E & CheckRelationExpression<ModelType<QBP>, E, QBP>,
1668
1914
  opt?: JoinRelatedOptions,
1669
1915
  ): QB;
1670
1916
  }
@@ -2033,11 +2279,24 @@ export interface ClearContextMethod<out QB extends AnyQueryBuilder> {
2033
2279
  }
2034
2280
 
2035
2281
  export interface ModifyMethod<out QB extends AnyQueryBuilder> {
2036
- (modifier: Modifier<QB> | Modifier<QB>[], ...args: any[]): QB;
2282
+ (modifier: ModifyArg<QB> | ModifyArg<QB>[], ...args: any[]): QB;
2037
2283
  }
2038
2284
 
2039
- export interface ModifiersMethod<out QB extends AnyQueryBuilder> {
2040
- (modifiers: Modifiers): QB;
2285
+ // Registered modifiers add up, on queries with and without a model class.
2286
+ type RegisterModifiers<X, O> =
2287
+ X extends QueryExtra<infer C, infer P, infer I>
2288
+ ? QueryExtra<C, P & O, I>
2289
+ : QueryExtra<unknown, O>;
2290
+
2291
+ export interface ModifiersMethod<
2292
+ out QB extends AnyQueryBuilder,
2293
+ M extends Model = ModelType<QB>,
2294
+ R = ResultType<QB>,
2295
+ X = ExtraType<QB>,
2296
+ > {
2297
+ <const O extends Modifiers>(
2298
+ modifiers: O,
2299
+ ): IfStrict<QB, Rebind<QB, M, R, RegisterModifiers<X, O>>>;
2041
2300
  (): QB;
2042
2301
  }
2043
2302
 
@@ -2051,7 +2310,11 @@ export interface CatchablePromiseLike<R> extends PromiseLike<R> {
2051
2310
  ): Promise<R | FR>;
2052
2311
  }
2053
2312
 
2054
- export class QueryBuilder<M extends Model, R = M[]> implements CatchablePromiseLike<R> {
2313
+ export class QueryBuilder<
2314
+ M extends Model,
2315
+ R = M[],
2316
+ X = unknown,
2317
+ > implements CatchablePromiseLike<R> {
2055
2318
  static forClass: ForClassMethod;
2056
2319
 
2057
2320
  constructor(modelClass: ModelConstructor<M>);
@@ -2240,35 +2503,35 @@ export class QueryBuilder<M extends Model, R = M[]> implements CatchablePromiseL
2240
2503
  groupBy: GroupByMethod<this>;
2241
2504
  groupByRaw: RawInterface<this>;
2242
2505
 
2243
- findById(id: MaybeCompositeId): MaybeSingleQueryBuilder<this>;
2244
- findByIds(ids: MaybeCompositeId[]): this;
2506
+ findById(id: IdArg<this>): MaybeSingleQueryBuilder<this>;
2507
+ findByIds(ids: IdArg<this>[]): this;
2245
2508
  findOne: WhereMethod<MaybeSingleQueryBuilder<this>>;
2246
2509
 
2247
2510
  execute(): Promise<R>;
2248
2511
  castTo<MC extends Model>(modelClass: ModelConstructor<MC>): QueryBuilderType<MC>;
2249
- castTo<R>(): QueryBuilder<M, R>;
2512
+ castTo<R>(): QueryBuilder<M, R, X>;
2250
2513
 
2251
2514
  update(update: PartialModelRow<M>): NumberQueryBuilder<this>;
2252
2515
  update(): NumberQueryBuilder<this>;
2253
2516
  updateById(
2254
- id: MaybeCompositeId,
2517
+ id: IdArg<this>,
2255
2518
  update: PartialModelRow<M>,
2256
2519
  ): NumberQueryBuilder<MaybeSingleQueryBuilder<this>>;
2257
2520
  updateAndFetch(update: PartialModelRow<M>): SingleQueryBuilder<this>;
2258
- updateAndFetchById(id: MaybeCompositeId, update: PartialModelRow<M>): SingleQueryBuilder<this>;
2521
+ updateAndFetchById(id: IdArg<this>, update: PartialModelRow<M>): SingleQueryBuilder<this>;
2259
2522
 
2260
2523
  patch(update: PartialModelPatch<M>): NumberQueryBuilder<this>;
2261
2524
  patch(): NumberQueryBuilder<this>;
2262
2525
  patchById(
2263
- id: MaybeCompositeId,
2526
+ id: IdArg<this>,
2264
2527
  update: PartialModelPatch<M>,
2265
2528
  ): NumberQueryBuilder<MaybeSingleQueryBuilder<this>>;
2266
2529
  patchAndFetch(update: PartialModelPatch<M>): SingleQueryBuilder<this>;
2267
- patchAndFetchById(id: MaybeCompositeId, update: PartialModelPatch<M>): SingleQueryBuilder<this>;
2530
+ patchAndFetchById(id: IdArg<this>, update: PartialModelPatch<M>): SingleQueryBuilder<this>;
2268
2531
 
2269
2532
  del(): NumberQueryBuilder<this>;
2270
2533
  delete(): NumberQueryBuilder<this>;
2271
- deleteById(id: MaybeCompositeId): NumberQueryBuilder<MaybeSingleQueryBuilder<this>>;
2534
+ deleteById(id: IdArg<this>): NumberQueryBuilder<MaybeSingleQueryBuilder<this>>;
2272
2535
 
2273
2536
  insert(insert: PartialModelRow<M>): SingleQueryBuilder<this>;
2274
2537
  insert(insert: PartialModelRow<M>[]): ArrayQueryBuilder<this>;
@@ -2292,15 +2555,15 @@ export class QueryBuilder<M extends Model, R = M[]> implements CatchablePromiseL
2292
2555
  // With literal relation expressions, the fetched relations become required
2293
2556
  // on the result type, see WithGraphQueryBuilder.
2294
2557
  withGraphFetched<const E extends RelationExpression<M>>(
2295
- expr: E & CheckRelationExpression<M, E>,
2558
+ expr: E & CheckRelationExpression<M, E, this>,
2296
2559
  options?: GraphOptions,
2297
2560
  ): WithGraphQueryBuilder<this, E>;
2298
2561
  withGraphJoined<const E extends RelationExpression<M>>(
2299
- expr: E & CheckRelationExpression<M, E>,
2562
+ expr: E & CheckRelationExpression<M, E, this>,
2300
2563
  options?: GraphOptions,
2301
2564
  ): WithGraphQueryBuilder<this, E>;
2302
2565
  withGraph<const E extends RelationExpression<M>>(
2303
- expr: E & CheckRelationExpression<M, E>,
2566
+ expr: E & CheckRelationExpression<M, E, this>,
2304
2567
  options?: WithGraphOptions,
2305
2568
  ): WithGraphQueryBuilder<this, E>;
2306
2569
 
@@ -2378,7 +2641,7 @@ export class QueryBuilder<M extends Model, R = M[]> implements CatchablePromiseL
2378
2641
  clearContext: ClearContextMethod<this>;
2379
2642
 
2380
2643
  modify: ModifyMethod<this>;
2381
- modifiers: ModifiersMethod<this>;
2644
+ modifiers: ModifiersMethod<this, M, R, X>;
2382
2645
 
2383
2646
  isFind: BooleanReturningMethod;
2384
2647
  isExecutable: BooleanReturningMethod;
@@ -2404,27 +2667,29 @@ export class QueryBuilder<M extends Model, R = M[]> implements CatchablePromiseL
2404
2667
 
2405
2668
  ModelType: M;
2406
2669
  ResultType: R;
2670
+ ExtraType: X;
2407
2671
 
2408
- // The model and result types that `RebindType` is instantiated with. They
2409
- // are `any` where `this` isn't rebound, e.g. when TypeScript checks the
2410
- // members of a custom query builder against this class.
2672
+ // The model, result and extra types that `RebindType` is instantiated
2673
+ // with. They are `any` where `this` isn't rebound, e.g. when TypeScript
2674
+ // checks the members of a custom query builder against this class.
2411
2675
  '~rebind': unknown;
2412
- '~M': this['~rebind'] extends [any, any] ? this['~rebind'][0] : any;
2413
- '~R': this['~rebind'] extends [any, any] ? this['~rebind'][1] : any;
2676
+ '~M': this['~rebind'] extends [any, any, any] ? this['~rebind'][0] : any;
2677
+ '~R': this['~rebind'] extends [any, any, any] ? this['~rebind'][1] : any;
2678
+ '~X': this['~rebind'] extends [any, any, any] ? this['~rebind'][2] : any;
2414
2679
 
2415
2680
  /**
2416
- * This query builder, re-instantiated with other model and result types,
2417
- * e.g. by `first()` or `page()`. Custom query builders redeclare it to keep
2418
- * their own type:
2681
+ * This query builder, re-instantiated with other model, result and extra
2682
+ * types, e.g. by `first()` or `page()`. Custom query builders redeclare it
2683
+ * to keep their own type:
2419
2684
  *
2420
- * declare RebindType: MyQueryBuilder<this['~M'], this['~R']>;
2685
+ * declare RebindType: MyQueryBuilder<this['~M'], this['~R'], this['~X']>;
2421
2686
  */
2422
- RebindType: QueryBuilder<this['~M'], this['~R']>;
2687
+ RebindType: QueryBuilder<this['~M'], this['~R'], this['~X']>;
2423
2688
 
2424
2689
  // The query builder rebound to the result kinds of the helper types, e.g.
2425
2690
  // `SingleQueryBuilder`. As members, they also resolve where `this` is
2426
2691
  // generic, e.g. in the methods of custom query builders. Of the members
2427
- // starting with `~`, only `~M` and `~R` are API, for `RebindType`.
2692
+ // starting with `~`, only `~M`, `~R` and `~X` are API, for `RebindType`.
2428
2693
  '~single': Rebind<this, M, ResultItem<R, M>>;
2429
2694
  '~maybeSingle': Rebind<this, M, ResultItem<R, M> | undefined>;
2430
2695
  '~array': Rebind<this, M, ResultItem<R, M>[]>;
@@ -2808,7 +3073,7 @@ export interface ModelClass<M extends Model> extends ModelConstructor<M> {
2808
3073
  relationMappings: RelationMappings | RelationMappingsThunk;
2809
3074
  modelPaths: string[];
2810
3075
  jsonAttributes: string[] | readonly string[];
2811
- virtualAttributes: string[];
3076
+ virtualAttributes: readonly string[];
2812
3077
  uidProp: string;
2813
3078
  uidRefProp: string;
2814
3079
  dbRefProp: string;
@@ -2921,7 +3186,7 @@ export class Model {
2921
3186
  static relationMappings: RelationMappings | RelationMappingsThunk;
2922
3187
  static modelPaths: string[];
2923
3188
  static jsonAttributes: string[] | readonly string[];
2924
- static virtualAttributes: string[];
3189
+ static virtualAttributes: readonly string[];
2925
3190
  static uidProp: string;
2926
3191
  static uidRefProp: string;
2927
3192
  static dbRefProp: string;
@@ -2949,16 +3214,28 @@ export class Model {
2949
3214
 
2950
3215
  static defaultGraphOptions?: GraphOptions;
2951
3216
 
2952
- static query<M extends Model>(
2953
- this: ConstructorType<M>,
3217
+ // The id column K is inferred separately from the class C, so that it
3218
+ // resolves through the constraint of a generic class, e.g. in static helpers
3219
+ // with `this: T`.
3220
+ static query<M extends Model, C = unknown, K = unknown>(
3221
+ this: ConstructorType<M> & IfStrict<unknown, C & { idColumn?: K }>,
2954
3222
  trxOrKnex?: TransactionOrKnex,
2955
- ): QueryBuilderType<M>;
3223
+ ): IfStrict<
3224
+ QueryBuilderType<M>,
3225
+ WithExtra<QueryBuilderType<M>, QueryExtra<C, {}, ColumnsId<K, M>>>
3226
+ >;
2956
3227
 
2957
- static relatedQuery<M extends Model, K extends keyof M>(
2958
- this: ConstructorType<M>,
3228
+ static relatedQuery<M extends Model, K extends keyof M, C = unknown>(
3229
+ this: ConstructorType<M> & IfStrict<unknown, C>,
2959
3230
  relationName: K,
2960
3231
  trxOrKnex?: TransactionOrKnex,
2961
- ): ArrayRelatedQueryBuilder<M[K]>;
3232
+ ): IfStrict<
3233
+ ArrayRelatedQueryBuilder<M[K]>,
3234
+ WithExtra<
3235
+ ArrayRelatedQueryBuilder<M[K]>,
3236
+ QueryExtra<MappedClass<ClassMappings<C>, K>, {}, ClassId<MappedClass<ClassMappings<C>, K>>>
3237
+ >
3238
+ >;
2962
3239
 
2963
3240
  static relatedQuery<RM extends Model>(
2964
3241
  relationName: string,
@@ -3215,32 +3492,26 @@ export interface initialize {
3215
3492
 
3216
3493
  /**
3217
3494
  * The type of the values that a JSON schema declared with `as const`
3218
- * describes, e.g. the properties of a model from its `jsonSchema`:
3495
+ * describes, e.g. of JSON columns, or of models that subclasses extend, whose
3496
+ * `jsonSchema` has the general type for `FromModel`:
3219
3497
  *
3220
- * class Person extends Model {
3221
- * static jsonSchema = {
3222
- * type: 'object',
3223
- * required: ['firstName'],
3224
- * properties: {
3225
- * id: { type: 'integer' },
3226
- * firstName: { type: 'string' },
3227
- * age: { type: ['integer', 'null'] },
3228
- * },
3229
- * } as const;
3498
+ * const entrySchema = { type: 'object', properties: { ... } } as const;
3230
3499
  *
3231
- * pets?: Animal[];
3500
+ * class Entry extends Model {
3501
+ * static jsonSchema: ReadonlyJSONSchema = entrySchema;
3232
3502
  * }
3233
3503
  *
3234
- * interface Person extends FromSchema<typeof Person.jsonSchema> {}
3504
+ * interface Entry extends FromJSONSchema<typeof entrySchema>, FromModel<typeof Entry> {}
3235
3505
  *
3236
3506
  * Supports `type` (also as an array), `required`, `nullable`, `enum`,
3237
3507
  * `const`, `items` (also as a tuple), nested `properties`, `anyOf` and
3238
- * `oneOf`. Formats like 'date-time' stay strings, as in JSON. Properties
3239
- * that aren't required are optional. Next to `type` or `properties`, `anyOf`
3240
- * and `oneOf` only add constraints, e.g. alternative `required` lists, and
3241
- * don't change the type.
3508
+ * `oneOf`. Formats like 'date-time' stay strings, as in JSON, see the
3509
+ * timestamps recipe for the dates that drivers return. Properties that aren't
3510
+ * required are optional. Next to `type` or `properties`, `anyOf` and `oneOf`
3511
+ * only add constraints, e.g. alternative `required` lists, and don't change
3512
+ * the type.
3242
3513
  */
3243
- export type FromSchema<S> = S extends boolean
3514
+ export type FromJSONSchema<S> = S extends boolean
3244
3515
  ? unknown
3245
3516
  : S extends { const: infer C }
3246
3517
  ? Extract<C, SchemaType<S>>
@@ -3251,7 +3522,7 @@ export type FromSchema<S> = S extends boolean
3251
3522
  : S extends { properties: object }
3252
3523
  ? SchemaObject<S>
3253
3524
  : S extends { anyOf: readonly (infer A)[] } | { oneOf: readonly (infer A)[] }
3254
- ? FromSchema<A> | SchemaNullable<S>
3525
+ ? FromJSONSchema<A> | SchemaNullable<S>
3255
3526
  : unknown;
3256
3527
 
3257
3528
  // The type that `type` allows, which `enum` and `const` values are narrowed
@@ -3280,7 +3551,7 @@ type SchemaTypeName<N, S> = N extends 'string'
3280
3551
  type SchemaArray<S> = S extends { items: infer I }
3281
3552
  ? I extends readonly unknown[]
3282
3553
  ? SchemaTuple<I, S>
3283
- : FromSchema<I>[]
3554
+ : FromJSONSchema<I>[]
3284
3555
  : unknown[];
3285
3556
 
3286
3557
  // Tuple `items` only check the items that are present: they are required
@@ -3288,12 +3559,12 @@ type SchemaArray<S> = S extends { items: infer I }
3288
3559
  // `additionalItems` restricts them.
3289
3560
  type SchemaTuple<I extends readonly unknown[], S> = [
3290
3561
  ...(S extends { minItems: I['length'] }
3291
- ? { -readonly [K in keyof I]: FromSchema<I[K]> }
3292
- : { -readonly [K in keyof I]?: FromSchema<I[K]> }),
3562
+ ? { -readonly [K in keyof I]: FromJSONSchema<I[K]> }
3563
+ : { -readonly [K in keyof I]?: FromJSONSchema<I[K]> }),
3293
3564
  ...(S extends { additionalItems: false }
3294
3565
  ? []
3295
3566
  : S extends { additionalItems: infer A }
3296
- ? FromSchema<A>[]
3567
+ ? FromJSONSchema<A>[]
3297
3568
  : unknown[]),
3298
3569
  ];
3299
3570
 
@@ -3305,16 +3576,97 @@ type SchemaObject<S> = S extends { properties: infer P }
3305
3576
 
3306
3577
  // The intersection is flattened into one object type for readable hovers.
3307
3578
  type SchemaProperties<P, R> = {
3308
- -readonly [K in keyof P as K extends R ? K : never]: FromSchema<P[K]>;
3579
+ -readonly [K in keyof P as K extends R ? K : never]: FromJSONSchema<P[K]>;
3309
3580
  } & {
3310
- -readonly [K in keyof P as K extends R ? never : K]?: FromSchema<P[K]>;
3581
+ -readonly [K in keyof P as K extends R ? never : K]?: FromJSONSchema<P[K]>;
3311
3582
  } extends infer O
3312
3583
  ? { [K in keyof O]: O[K] }
3313
3584
  : never;
3314
3585
 
3586
+ /**
3587
+ * The properties of a model class, derived from its `jsonSchema` declared
3588
+ * `as const` and its `relationMappings`:
3589
+ *
3590
+ * class Person extends Model {
3591
+ * static jsonSchema = { ... } as const;
3592
+ *
3593
+ * static relationMappings = () => ({
3594
+ * parent: {
3595
+ * relation: Model.BelongsToOneRelation,
3596
+ * modelClass: Person,
3597
+ * join: { from: 'persons.parentId', to: 'persons.id' },
3598
+ * },
3599
+ * });
3600
+ * }
3601
+ *
3602
+ * interface Person extends FromModel<typeof Person> {}
3603
+ *
3604
+ * The properties are typed as with `FromJSONSchema`. Relations are optional, as
3605
+ * they are only present when fetched: to-one relations are typed `M | null`,
3606
+ * as `withGraphFetched()` sets missing ones to `null`, and to-many relations
3607
+ * `M[]`. Relations with a module path as `modelClass`, and mappings declared
3608
+ * as `RelationMapping` instead of inferred, are left out, as are a schema and
3609
+ * mappings with their general types, e.g. inherited from `Model`. The names of
3610
+ * `virtualAttributes` declared `as const` are kept for `ModelJson`.
3611
+ */
3612
+ export type FromModel<C extends { jsonSchema: unknown; relationMappings: unknown }> =
3613
+ ModelSchemaProperties<C['jsonSchema']> &
3614
+ ModelRelationProperties<C['relationMappings']> &
3615
+ VirtualAttributeKeys<C>;
3616
+
3617
+ // One key per name, so that subclasses that add virtual attributes have the
3618
+ // same keys as their parent for the inherited ones.
3619
+ type VirtualAttributeKeys<C> = C extends { virtualAttributes: infer V extends readonly string[] }
3620
+ ? string extends V[number]
3621
+ ? {}
3622
+ : { readonly [K in V[number] as `~virtual:${K}`]?: true }
3623
+ : {};
3624
+
3625
+ type ModelSchemaProperties<S> = ReadonlyJSONSchema extends S
3626
+ ? {}
3627
+ : FromJSONSchema<S> extends infer P extends object
3628
+ ? P
3629
+ : {};
3630
+
3631
+ // Relations are picked by their mappings, as resolving the related models
3632
+ // while the model's own type is built would make it its own base type.
3633
+ type ModelRelationProperties<T, R = T extends () => infer M ? M : T> = string extends keyof R
3634
+ ? {}
3635
+ : {
3636
+ -readonly [K in keyof R as IsMappedRelation<R[K]> extends true ? K : never]?: MappedRelation<
3637
+ R[K]
3638
+ >;
3639
+ };
3640
+
3641
+ // Relation types without a brand and module paths can't be resolved.
3642
+ type IsMappedRelation<M> = M extends { relation: infer T; modelClass: infer C }
3643
+ ? [MappedKind<T>] extends [never]
3644
+ ? false
3645
+ : C extends string
3646
+ ? false
3647
+ : true
3648
+ : false;
3649
+
3650
+ // The related model is inferred from the constructor without checking it
3651
+ // against `Model`, as that check would circle back through the relations of
3652
+ // self-referencing models.
3653
+ type MappedRelation<M> = M extends { relation: infer T; modelClass: infer C }
3654
+ ? MappedKind<T> extends 'HasMany' | 'ManyToMany' | 'BelongsToMany'
3655
+ ? MappedModel<C>[]
3656
+ : MappedModel<C> | null
3657
+ : never;
3658
+
3659
+ type MappedKind<T> = T extends { readonly [relationKind]?: infer K } ? K : never;
3660
+
3661
+ type MappedModel<C> = C extends abstract new (...args: any) => infer I
3662
+ ? I
3663
+ : C extends () => abstract new (...args: any) => infer I
3664
+ ? I
3665
+ : never;
3666
+
3315
3667
  /**
3316
3668
  * A JSON schema with readonly arrays, the type of `static jsonSchema`, so that
3317
- * it takes schemas declared `as const`, for `FromSchema`. A `JSONSchema` is
3669
+ * it takes schemas declared `as const`, for `FromJSONSchema`. A `JSONSchema` is
3318
3670
  * assignable to it.
3319
3671
  */
3320
3672
  export type ReadonlyJSONSchema = ReadonlyDeep<JSONSchema>;