Skip to content

Question about TypeScript typing of query override #2022

@erwinv

Description

@erwinv

In the Custom query builder guide, extending the query builder in TypeScript is achieved with the following workaround:

import {
  Model,
  Page,
  QueryBuilder,
} from 'objection'

export class MyModel extends Model {
  static get QueryBuilder() {
    return class MyModelCustomQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<M, R> {
      ArrayQueryBuilderType!: MyModelCustomQueryBuilder<M, M[]>;
      SingleQueryBuilderType!: MyModelCustomQueryBuilder<M, M>;
      NumberQueryBuilderType!: MyModelCustomQueryBuilder<M, number>;
      PageQueryBuilderType!: MyModelCustomQueryBuilder<M, Page<M>>;

      constructor(...args: [arg: any]) { // 1-element tuple, ugly hack/workaround
        super(...args)

        this.onBuild(builder => {
          const { withArchived = false } = builder.context()
          if (!withArchived) {
            builder.andWhere('is_archived', false)
          }
        })
      }
    }
  }
}

It works, but the guide also suggests just overriding the query static method since I am not adding methods to the custom query builder and only modifying the query behavior (specifically, automatically adding the is_archived = false WHERE condition on all queries by default).

I cannot, however, make the static query override pass the TypeScript type-check. Here's my latest attempt:

import {
  Model,
  Page,
  QueryBuilder,
} from 'objection'

export class MyModel extends Model {
  // this would've been shorter, but it wouldn't compile due to https://github.com/microsoft/TypeScript/issues/4628 :(
  static query(...args: any[]) {
    return super.query<MyModel>(...args)
      .onBuild(builder => {
        const { withArchived = false } = builder.context()
        if (!withArchived) {
          builder.andWhere('is_archived', false)
        }
      })
  }
}

The error from the TypeScript compiler:

Class static side 'typeof MyModel' incorrectly extends base class static side 'typeof Model'.
  The types returned by 'query(...)' are incompatible between these types.
    Type 'QueryBuilder<MyModel, MyModel[]>' is not assignable to type 'QueryBuilderType<M>'.
      Type 'QueryBuilder<MyModel, MyModel[]>' is not assignable to type 'QueryBuilder<M, M[]>'.
        The types returned by 'findById(...).execute()' are incompatible between these types.
          Type 'Promise<MyModel>' is not assignable to type 'Promise<M>'.
            Type 'MyModel' is not assignable to type 'M'.
              'M' could be instantiated with an arbitrary type which could be unrelated to 'MyModel'.ts(2417)

My question is:

  1. Is there a way to make this work? Am I missing a type-coercion trick on my code?
  2. If not, is the Model base class static query method typing incorrect (or can it be improved to make this work)?

Or is there nothing that can be done because this is just a natural limitation of microsoft/TypeScript#4628?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions