Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
- In strict mode, `findById()`, `findByIds()` and the other `*ById()` methods take the types of the properties of a literal `idColumn` in queries started from model classes, e.g. `[number, string]` for `static idColumn = ['groupId', 'memberId'] as const`. See [models](/guide/models.md#checking-queries-in-typescript) [#432](https://github.com/ditojs/objection/issues/432)
- `ModelProps` and `ModelRelations` tell properties and relations apart like the other types: nullable and readonly relations are relations, and properties typed `any` or `any[]` aren't, so loose mode no longer suggests relations as columns in queries
- `ModelClass<M>`, the type of `modelClass()` and `$modelClass`, accepts `transaction(undefined, callback)`, like `Model`
- `Parameters<>` of `whereIn()` takes all its values, not only query builders, as its other overloads do
- `patch()`, `patchById()`, `patchAndFetch()`, `patchAndFetchById()` and `merge()` take field expression keys of the model's columns, like `'address:street'`, typed as `PartialModelPatch<M>`. `insert()` and `update()` still reject them.

### Other
Expand Down
30 changes: 30 additions & 0 deletions tests/ts/parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Type tests for wrappers that forward their arguments to the query builder
// methods through `Parameters<>`, which only sees the last overload.

import { QueryBuilder, raw } from 'objection';
import { Person } from './fixtures/person.js';

function where(...args: Parameters<QueryBuilder<Person>['where']>) {
return Person.query().where(...args);
}

function findOne(...args: Parameters<QueryBuilder<Person>['findOne']>) {
return Person.query().findOne(...args);
}

function whereIn(...args: Parameters<QueryBuilder<Person>['whereIn']>) {
return Person.query().whereIn(...args);
}

function select(...args: Parameters<QueryBuilder<Person>['select']>) {
return Person.query().select(...args);
}

(async () => {
await where({ firstName: 'Jennifer', unknown: 1 });
await findOne({ firstName: 'Jennifer' });
await whereIn('id', [1, 2]);
await whereIn(['id', 'firstName'], Person.query().select('id', 'firstName'));
await select('id', 'firstName', raw('1'));
await select(['id', 'firstName']);
})();
19 changes: 19 additions & 0 deletions tests/ts/strict/columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,25 @@ async function unchecked(column: string, personColumn: 'firstName' | 'lastName')
await Untyped.query().where('anything', 1).orderBy('anything');
}

// Pin what wrappers that forward their arguments through `Parameters<>` get:
// the last overload, which takes where objects without checking their keys.
// The literal column checks of `select()` and `whereIn()` can't be forwarded:
// `Parameters<>` of `select()` is `unknown[]`, so such a wrapper accepts any
// arguments, and fails to compile inside its body.

function where(...args: Parameters<QueryBuilder<Person>['where']>) {
return Person.query().where(...args);
}

function findOne(...args: Parameters<QueryBuilder<Person>['findOne']>) {
return Person.query().findOne(...args);
}

async function forwarded() {
await where({ firstName: 'Jennifer', unknown: 1 });
await findOne({ firstName: 'Jennifer', unknown: 1 });
}

// Generic code is checked loosely, as the columns can't be resolved there.

export class GenericQueryBuilder<M extends Model, R = M[], X = unknown> extends QueryBuilder<
Expand Down
22 changes: 12 additions & 10 deletions typings/objection/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1536,15 +1536,14 @@ export interface SelectMethod<out QB extends AnyQueryBuilder> {
<QBP extends QB>(...columns: IfStrict<Selection<QBP>, never>[]): QB;
<QBP extends QB>(columns: IfStrict<Selection<QBP>, never>[]): QB;

// Allow anything else in loose mode, like `select(1)`:
<QBP extends QB>(...args: IfStrict<{} | null | undefined, never>[]): QB;

<QBP extends QB, const C extends readonly unknown[] = []>(
...columns: StrictSelections<ModelType<QBP>, C>
): SelectQueryBuilder<QBP, C>;
<QBP extends QB, const C extends readonly unknown[] = []>(
columns: StrictSelections<ModelType<QBP>, C>,
): SelectQueryBuilder<QBP, C>;
// Allow anything else in loose mode, like `select(1)`. The last overload
// is what `Parameters<>` sees, so it takes the loose arguments:
<QBP extends QB, const C extends readonly unknown[] = []>(
...columns: IfStrict<({} | null | undefined)[], StrictSelections<ModelType<QBP>, C>>
): IfStrict<QB, SelectQueryBuilder<QBP, C>>;
}

export interface AsMethod<out QB extends AnyQueryBuilder> {
Expand Down Expand Up @@ -1654,9 +1653,10 @@ export interface WhereMethod<out QB extends AnyQueryBuilder> {

<QBP extends QB>(obj: IfStrict<PartialModelProps<ModelType<QBP>>, never>): QB;
// Allow any keys in loose mode. The model's properties above only serve
// autocompletion.
(obj: IfStrict<object, never>): QB;
<QBP extends QB, const O extends object = {}>(obj: O & StrictWhereObject<ModelType<QBP>, O>): QB;
// autocompletion. The last overload is what `Parameters<>` sees:
<QBP extends QB, const O extends object = {}>(
obj: IfStrict<object, O & StrictWhereObject<ModelType<QBP>, O>>,
): QB;
}

export interface WhereLikeMethod<out QB extends AnyQueryBuilder> {
Expand Down Expand Up @@ -1701,8 +1701,10 @@ export interface WhereInMethod<out QB extends AnyQueryBuilder> {
col: StrictColumn<ModelType<QBP>, C>,
expr: WhereInValues<QB, QBP, C>,
): QB;
// The last overload is what `Parameters<>` sees, so it takes the loose
// columns in loose mode:
<QBP extends QB, const C extends readonly unknown[] = []>(
col: StrictColumns<ModelType<QBP>, C>,
col: IfStrict<RefArg<QB> | RefArg<QB>[], StrictColumns<ModelType<QBP>, C>>,
expr: readonly Expression<PrimitiveValue>[] | Raw | AnyQueryBuilder | CallbackVoid<QB>,
): QB;
}
Expand Down
Loading