objection.js
objection.js copied to clipboard
Custom QueryBuilder with "constructor" in TypeScript
"objection": "^3.0.1"
"knex": "^2.1.0"
I am converting an existing code base from JavaScript to TypeScript. My apologies if this was already answered. I did search the issues but was unable to find an answer so I am going to ask it here.
Existing JavaScript code
My existing JavaScript code for base-model.js
works fine and is based on https://github.com/Vincit/objection.js/issues/85#issuecomment-185183032 and looks like this:
import { Model, QueryBuilder, AjvValidator } from "objection";
import addFormats from "ajv-formats";
class DefaultSchemaQueryBuilder extends QueryBuilder {
constructor(modelClass) {
super(modelClass);
if (modelClass.defaultSchema) {
this.withSchema(modelClass.defaultSchema);
}
}
}
export default class BaseModel extends Model {
static get QueryBuilder() {
return DefaultSchemaQueryBuilder;
}
static createValidator() {
return new AjvValidator({
onCreateAjv: (ajv) => {
addFormats(ajv);
},
options: {
allErrors: true,
validateSchema: false,
ownProperties: true,
v5: true,
},
});
}
$formatDatabaseJson(json) {
// Don't forget to call super
json = super.$formatDatabaseJson(json);
// do stuff
// ...
return json;
}
$parseDatabaseJson(json) {
// Don't forget to call super
json = super.$parseDatabaseJson(json);
// do stuff
// ...
return json;
}
}
Converted TypeScript code
My attempt at converting it to TypeScript (base-model.ts
) is currently blocked by the errors I am showing in the comments in the code below:
import { Model, Page, QueryBuilder, AjvValidator } from "objection";
import addFormats from "ajv-formats";
class DefaultSchemaQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<
M,
R
> {
ArrayQueryBuilderType!: DefaultSchemaQueryBuilder<M, M[]>;
SingleQueryBuilderType!: DefaultSchemaQueryBuilder<M, M>;
MaybeSingleQueryBuilderType!: DefaultSchemaQueryBuilder<M, M | undefined>;
NumberQueryBuilderType!: DefaultSchemaQueryBuilder<M, number>;
PageQueryBuilderType!: DefaultSchemaQueryBuilder<M, Page<M>>;
// I am getting the following warning:
//
// (parameter) modelClass: any
// Parameter 'modelClass' implicitly has an 'any' type, but a better type may be inferred from usage.ts(7044)
constructor(modelClass) {
// I am getting the following error:
//
// (parameter) modelClass: any
// Expected 0 arguments, but got 1.ts(2554)
super(modelClass);
if (modelClass.defaultSchema) {
this.withSchema(modelClass.defaultSchema);
}
}
}
// I am getting the following error from the linter:
//
// class BaseModel
// Class static side 'typeof BaseModel' incorrectly extends base class static side 'typeof Model'.
// Types of property 'QueryBuilder' are incompatible.
// Type 'typeof DefaultSchemaQueryBuilder' is not assignable to type 'typeof QueryBuilder'.
// Types of construct signatures are incompatible.
// Type 'new <M extends Model, R = M[]>(modelClass: any) => DefaultSchemaQueryBuilder<M, R>' is not assignable to type 'new <M extends Model, R = M[]>() => QueryBuilder<M, R>'.ts(2417)
export default class BaseModel extends Model {
QueryBuilderType!: DefaultSchemaQueryBuilder<this>;
static QueryBuilder = DefaultSchemaQueryBuilder;
static createValidator() {
return new AjvValidator({
onCreateAjv: (ajv) => {
addFormats(ajv);
},
options: {
allErrors: true,
validateSchema: false,
ownProperties: true,
},
});
}
$formatDatabaseJson(json: any) {
json = super.$formatDatabaseJson(json);
// do stuff
// ...
return json;
}
$parseDatabaseJson(json: any) {
json = super.$parseDatabaseJson(json);
// do stuff
// ...
return json;
}
}
Question:
My question is how would you specify types for the constructor part in base-model.ts
:
constructor(modelClass) {
super(modelClass);
if (modelClass.defaultSchema) {
this.withSchema(modelClass.defaultSchema);
}
}
Does anyone have a fix for this, I have the same error.
I found a way around this compile error with @ts-ignore
//@ts-ignore
export class MyQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<
M,
R
> {
ArrayQueryBuilderType!: MyQueryBuilder<M, M[]>
SingleQueryBuilderType!: MyQueryBuilder<M, M>
MaybeSingleQueryBuilderType!: MyQueryBuilder<M, M | undefined>
NumberQueryBuilderType!: MyQueryBuilder<M, number>
PageQueryBuilderType!: MyQueryBuilder<M, Page<M>>
//@ts-ignore
constructor(modelClass) {
// @ts-ignore
super(modelClass)
this.onBuild(function(builder) {
if (builder.isFind() && !builder.context().softDelete) {
builder.whereNull('deleted_at')
}
})
}
}
Do not use contructor.
Use forClass
static variable.
const buildQueryBuidlerForClass = (scope) => {
return (modelClass) => {
const qb = QueryBuilder.forClass.call(scope, modelClass);
qb.onBuild((builder) => {
...
});
return qb as any;
};
};
class YourQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<M, R> {
...
static forClass: ForClassMethod = buildQueryBuidlerForClass(this);
}