diff --git a/doc/release-notes/changelog.md b/doc/release-notes/changelog.md index 002753e2f..3a4280bab 100644 --- a/doc/release-notes/changelog.md +++ b/doc/release-notes/changelog.md @@ -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`, 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`. `insert()` and `update()` still reject them. ### Other diff --git a/tests/ts/parameters.ts b/tests/ts/parameters.ts new file mode 100644 index 000000000..d2cdda618 --- /dev/null +++ b/tests/ts/parameters.ts @@ -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['where']>) { + return Person.query().where(...args); +} + +function findOne(...args: Parameters['findOne']>) { + return Person.query().findOne(...args); +} + +function whereIn(...args: Parameters['whereIn']>) { + return Person.query().whereIn(...args); +} + +function select(...args: Parameters['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']); +})(); diff --git a/tests/ts/strict/columns.ts b/tests/ts/strict/columns.ts index 468ab65d8..925b3f048 100644 --- a/tests/ts/strict/columns.ts +++ b/tests/ts/strict/columns.ts @@ -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['where']>) { + return Person.query().where(...args); +} + +function findOne(...args: Parameters['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 extends QueryBuilder< diff --git a/typings/objection/index.d.ts b/typings/objection/index.d.ts index 689c41209..c85705ddb 100644 --- a/typings/objection/index.d.ts +++ b/typings/objection/index.d.ts @@ -1536,15 +1536,14 @@ export interface SelectMethod { (...columns: IfStrict, never>[]): QB; (columns: IfStrict, never>[]): QB; - // Allow anything else in loose mode, like `select(1)`: - (...args: IfStrict<{} | null | undefined, never>[]): QB; - - ( - ...columns: StrictSelections, C> - ): SelectQueryBuilder; ( columns: StrictSelections, C>, ): SelectQueryBuilder; + // Allow anything else in loose mode, like `select(1)`. The last overload + // is what `Parameters<>` sees, so it takes the loose arguments: + ( + ...columns: IfStrict<({} | null | undefined)[], StrictSelections, C>> + ): IfStrict>; } export interface AsMethod { @@ -1654,9 +1653,10 @@ export interface WhereMethod { (obj: IfStrict>, never>): QB; // Allow any keys in loose mode. The model's properties above only serve - // autocompletion. - (obj: IfStrict): QB; - (obj: O & StrictWhereObject, O>): QB; + // autocompletion. The last overload is what `Parameters<>` sees: + ( + obj: IfStrict, O>>, + ): QB; } export interface WhereLikeMethod { @@ -1701,8 +1701,10 @@ export interface WhereInMethod { col: StrictColumn, C>, expr: WhereInValues, ): QB; + // The last overload is what `Parameters<>` sees, so it takes the loose + // columns in loose mode: ( - col: StrictColumns, C>, + col: IfStrict | RefArg[], StrictColumns, C>>, expr: readonly Expression[] | Raw | AnyQueryBuilder | CallbackVoid, ): QB; }