Reputation: 6172
Note: this question extends Property decorator only for a specific property type
--
My goal is to design a simple ORM, in which a model can be defined as such:
@model.model()
class User extends model.Model {
@model.attr(model.StringType)
user: string;
@model.attr(model.StringType)
age: number;
}
In this model, I want the compiler to throw a type error, as StringType
cannot be applied to an attribute of type number
. I cannot however get this to work properly -- no error is thrown.
My code so far:
interface Constructor<T> {
new (...args: any[]): T;
}
abstract class BaseType<T> {}
class StringType extends BaseType<string> {}
type RecordKey = string | symbol;
export function attr<T>(type: Constructor<BaseType<T>>) {
return <K extends RecordKey, C extends Record<K, T>>(ctor: C, key: K) => {
console.log(ctor, key);
}
}
Some pointers on how this code is not working would be incredibly useful here.
Upvotes: 2
Views: 938
Reputation: 3506
Typescript is structurally typed which is the key to this whole answer. In this case it means any plain object can be a BaseType<T>
because it has no fields/methods. Notice the inferred type reports {}
for T
in @model.attr(model.StringType)
in the playground.
The following assignment is perfectly fine (and number
can be anything):
abstract class BaseType<T> { }
const t: BaseType<number> = { };
To require that the generic is specified try:
function attr<T = never>(type: Constructor<BaseType<T>>) { ... }
// ...
@model.attr<string>(model.StringType)
user: string
but note that it will allow @model.attr<number>(model.StringType)
because again, any object is a BaseType<number>
.
If your BaseType
interface is not yet complete, keep filling it out and you might find that there is enough for typescript to latch on to and infer from. For example (completely arbitrary but so you get the idea and can test the inference):
abstract class BaseType<T> {
deserialize: (s: string) => T;
}
Upvotes: 1