node.js - TypeScript: Decorando uma classe derivada com função decoradora tipada
Estou tentando construir uma biblioteca ORM semelhante ao "Entity Framework" para node.js e MongoDB com TypeScript.
Com esta biblioteca, o consumidor poderá definir uma classe Model (ex. Person) que irá estender uma classe Base, e o decorador de classe adicionará dados adicionais à classe Person (por exemplo, o array instances, que conterá todas as instâncias do Model, e collection_name que será o nome da coleção do MongoDB para o modelo, etc.).
Código no playground de TypeScript.
Então meu primeiro passo foi criar uma classe Base:
class Base<T>
{
static collection_name: string
static instances: Base<any>[]
_id: string
}
Então o usuário poderá definir seu modelo assim:
@decorator<Person>({collection_name: 'people'})
class Person extends Base<Person>
{
@field name
@field address
...
}
então eu criei uma classe decoradora para definir as propriedades collection_name e instances na classe Person:
function decorator<T>(config: { collection_name: string }) {
return function <T extends Base<T>>(Class: IClass<T>) {
Class.collection_name = config.collection_name;
Class.instances = [];
return Class
}
}
a função decoradora recebe a Class gerada pelo usuário, e eu estou tentando criar uma interface que descreva o tipo de tal classe. Eu a chamei de IClass:
interface IClass<T>
{
new(): Base<T>
instances: Base<T>[];
collection_name: string
}
newé o construtor (que retorna uma instância deBase)instancesecollection_namesão propriedades estáticas deBase<T>e são não estáticas aqui (não tenho certeza sobre isso, isso está certo?)
No entanto, ao tentar definir o modelo de usuário, recebo o seguinte erro:
@decorator<Person>({collection_name: 'people'}) // <==== ERROR HERE ===
class Person extends Base<Person>
{
}
Erro:(23, 2) TS2345: Argumento do tipo 'typeof Person' não pode ser atribuído ao parâmetro do tipo 'IClass>'.
A propriedade 'instances' está ausente no tipo 'typeof Person', mas o tipo 'typeof Person' está ausente das seguintes propriedades do tipo exigido em 'IClass>'.
Parece que o compilador TypeScript está ignorando os membros estáticos herdados de Base ao verificar o tipo de typeof Person.
Como posso definir o tipo da propriedade da Classe da função decoradora?
Resposta #1
O problema, como jcalz aponta, é que seu decorator está aceitando uma Class de um tipo que já possui as propriedades estáticas instances e collection_name. Você precisa usar duas interfaces diferentes, uma que seja um tipo que simplesmente construa instâncias de T com a assinatura new(): T, e outra que estenda essa interface para incluir as propriedades estáticas que seu decorator irá adicionar.
class Base<T> {
static _id = 0;
private _id: number;
constructor () {
this._id = Base._id++;
}
}
interface BaseConstructor<T extends Base<T>> {
_id: number;
new(): T;
}
interface DecoratedBaseConstructor<T extends Base<T>> extends BaseConstructor<T> {
instances: T[];
collection_name: string;
}
function decorator<T extends Base<T>>(config: { collection_name: string }) {
return (baseConstructor: BaseConstructor<T>): DecoratedBaseConstructor<T> => {
const decoratedBaseConstructor = baseConstructor as Partial<DecoratedBaseConstructor<T>>;
decoratedBaseConstructor.collection_name = config.collection_name;
decoratedBaseConstructor.instances = [];
return decoratedBaseConstructor as DecoratedBaseConstructor<T>;
};
}
@decorator<Person>({collection_name: 'people'})
class Person extends Base<Person> {
name: string;
constructor () {
super();
this.name = 'foo';
}
}
Com essa abordagem, todos os membros static de Base devem ser públicos. Qualquer membro static de Base inicializado no decorator deve ir para o DecoratedBaseConstructor, e quaisquer membros static restantes que não forem inicializados no decorator devem ir para o BaseConstructor em vez disso.
Eu suponho que você use o tipo genérico T na classe Base de alguma forma no seu código atual, mas se você não usar, você deve remover o tipo genérico da classe Base e todo o resto ainda funcionará da mesma forma.
Confira o trecho acima neste playground.
