Given the following code:
import { Pool } from 'pg';
const DB = new Pool();
interface IUser {
id: number;
email: string;
name: string
}
type Constructable<T> = new () => T;
class QueryBuilder<T> {
constructor(private model: Constructable<T>) {
this.model = model;
}
//bunch of methods here like
// where, whereNull, whereNotNull, limit, orderBy, .. which are all returning `this` for the purpose of "chainability"
async first(): Promise<T> {
const { rows } = await DB.query('SELECT * FROM ... LIMIT 1');
const instance = new this.model();
Object.assign(instance, rows[0]);
return instance;
}
}
class Model<T> {
table = ''
static qb(): QueryBuilder<T> {
return new QueryBuilder<T>(this);
}
}
class User extends Model<IUser> {}
What I'm trying to achieve is an API similar to the following (in a controller for example)
import User from '../models/user';
const user1 = User.where('id', 1).first();
console.log(user1);
would return something like:
User {
id: 1,
email: 'john.doe@example.com',
name: 'John Doe',
}
The problem:
static qb(): QueryBuilder<T> {
return new QueryBuilder<T>(this);
}
In this context <T> cannot be used. Error: Static members cannot reference class type parameters.ts(2302)
The workaround is changing <T> to <any> but then I'd have no more type checking onwards when I would like to do something like:
const user2 = User.where('id', 2).first();
user2.name = 'new name';
user2.save();
where save() should be a non static function on the Model class (as I'd then have an instance of the User)
I've looked into several similar answers in SO but with what I've tried so far I couldn't create a instance of the <T> class inside the QueryBuilder any more (because of what this becomes if the class becomes abstract).
Any help is greatly appreciated!
LATER EDIT: With other words, if I make the Model class abstract, how do I reference the class that extends (as this keyword cannot be instantiated when the Model class is abstract)