TypeScript Issue-Proposal
TypeScript 2.6.2
What follow are proposals for TypeScript. If follow a well know discussion, rather official #3628. It propose handling [1] abstract class decorators, abstract members decorators, generic metadata, and [2] constructor typing, and abstract class expression
[1]: Abstract class decorators, abstract members decorators, generic metadata
As to me, it is clear that by no mean should Interface exist at runtime as far as ECMA doesn't consider it worth runtime-life. However, need for this feature is more and more demanded, the reason is simple for me: more poeple are moving from JAVA to TypeScript (steping over Javascript) and so will it be with Kotlin, which they bitterly defend already: I wonder if they face the same challenging Interface DI when transpiling to Javascript.
Initial scenario
exportinterfaceCrudLike<EextendsEntity>{search(id: ObjectID): PromiseLike<E>;drop(entity: E): PromiseLike<void>;save(entity: E): PromiseLike<E>;search(): PromiseLike<E[]>;}publicclassProductRepositoryimplementsRepositoryLike<Product>{
@Inject()publiccategoryCrud: CrudLike<Category>;
@Inject()publicproductCrud: CrudLike<Product>;}Writing such code is very difficult, not to say impossible in typescript, at least, using the official transpiler. Keeping in mind that no interface should be decorated at the moment, my suggest should make this possible :
Scenario -001
@CustomCRUD()exportabstractclassCrud<EextendsEntity>implementsCrudLike<E>{}In this case, generated metadata following @CustomCrud() should describe methods and attibutes from CrudLike which the abstract class implements.
Scenario -002
@CustomDAO()exportabstractclassDao<EextendsEntity>{
@SqlRequest('INSERT INTO ...')publicabstractcreate(entity: E): PromiseLike<E>;
@SqlRequest('SELECT E.* FROM ...')publicabstractsearch(): PromiseLike<E>;
@SqlRequest('SELECT E.* FROM ... WHERE id = E.id')publicabstractsearch(id: number): PromiseLike<E>;}This second senario just improves the first one, enabling us to write things like :
@DiFactory(daoFactory)
@CustomDAO()exportabstractclassDao<EextendsEntity>{//...}functiondaoFactory<EextendsEntity,Gextends{new(...args: any[]): E}>(generic: G): Dao<E>{//...// the injector may rely on this factory to forge clean instances// tis factory can even rely on metadata to enforce adequate method implementations//...}// ...and later onclassUsefulClass{
@Inject()privateproductDao: Dao<Product>;
@Inject()privatecategoryDao: Dao<Category>;}A word to conclude : the proposal
It come up that this would be possible :
- When
abstract class will support decorators - When
decorator could be applied to abstract members of abstract classes - When generated
metadata will describe even generic types (If TS support complex generics, it means they can be modeled and represented, thus supported)
I really wonder why such constraints were first enforced !
[2]: constructor typing, and abstract class expression
TypeScript documentation proudly exhibit this fact :
One of TypeScript’s core principles is that type-checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.
Please, stop when I am wrong. This statement also implies that the following are identical :
interfaceNameableLike{name: string;}// andtypeNameableLike={name: string;};Scenario -001 : Constructor typing
classType{}interfaceTypeConstructor{new(): Type;}functionfn_001(clazz: TypeConstructor): TypeConstructor{returnclassextendsclazz{};}functionfn_002(clazz: {new(): Type}): TypeConstructor{returnclassextendsclazz{};}/** * * Tests serie #001 * */fn_001(Type);fn_002(Type);/** * * Tests serie #002 * */classAnotherType{}interfaceAnotherTypeConstructorextendsTypeConstructor{new(): AnotherType;}fn_001(AnotherType);fn_002(AnotherType);The decorators fn_001 and fn_002 where type enforced to work with Type and are now traped to accept even AnotherType. If one say this design is backed by no logic, I answer that typing was introduced for best practice and early violation detection. The question that springs up now is [W]hat typescript construct will ensure for sure that our constructor is for Type and Type only.
Scenario -002 : abstract class expression
The following scenario is a simplified image of my actual case, which is much more complicated.
abstractclassMuchAbstraction{publicabstractgetidentifier(): string;}abstractclassKindaLessAbstractionextendsMuchAbstraction{// `identifier` cannot be implemented here as it is generated at runtime}functionfn_001(clazz: {new(): MuchAbstraction}){returnclassextendsclazz{publicconstructor(){super();// someLogic(); //...}}}Take note that even fn_001 SHOULD NOT implement identifier accessor as it is not its reponsibility: another decorator ensures it. As some may have seen, abstract keyword isn't allowed in class expression which is a limitation.
The following isn't a solution !
interfaceMuchAbstractionConstructor{new(): MuchAbstraction;}functionfn_002(clazz: MuchAbstractionConstructor){returnclassextendsclazz// same error for abstract classes{publicconstructor(){super();// someLogic(); //...}}}What follow works well, BUT a 'one decorator - all purpose' isn't a great design.
functionfn_003(clazz: MuchAbstractionConstructor){returnclassextendsclazz{publicconstructor(){super();// someLogic(); //...}publicgetidentifier(): string{return<any>void0;}}}However, the following calls are rejected : ask me why ?
// 'typeof MuchAbstraction' is not assignable to parameter of type 'MuchAbstractionConstructor'fn_003(MuchAbstraction);// error// 'typeof KindaLessAbstraction' is not assignable to parameter of type 'MuchAbstractionConstructor'fn_003(KindaLessAbstraction);// error
In fact, none of the above fn_001 and fn_002 works when called.
functionfn_004_from_fn_001(clazz: {new(): MuchAbstraction}){}functionfn_004_from_fn_002(clazz: MuchAbstractionConstructor){}fn_004_from_fn_001(MuchAbstraction);// errorfn_004_from_fn_002(MuchAbstraction);// errorHowever, the following works great :
classNoAbstractionextendsKindaLessAbstraction{publicgetidentifier(): string{return<any>void0;}}fn_003(NoAbstraction);// function fn_003(clazz: MuchAbstractionConstructor) { /** some logic **/ }Conclusion: Even constructor construct to target static side doesn't accept abstract classes. Where then is the shape or duck typing proned in TypeScript documentation? Or where it does stop (I have not found in docs).
This somehow take us to the question in scenario -001 : constructor typing.
Nonetheless, I was able to come up with something that works :
functionfn_004_from_fn_003(clazz: typeofMuchAbstraction){}fn_004_from_fn_003(MuchAbstraction);The following implementation leads to unsuspected results : everything works well.
functionfn_005(clazz: typeofMuchAbstraction){}leta=fn_005(MuchAbstraction);fn_005(KindaLessAbstraction);fn_005(NoAbstraction);This encouraged me to go a little further :
functionfn_006(clazz: typeofMuchAbstraction): typeofMuchAbstraction{returnclassextendsclazz{publicconstructor(){super();// someLogic(); //...}publicgetidentifier(): string{return<any>void0;}}}constMyLessToNoAbstraction: typeofMuchAbstraction=fn_005(MuchAbstraction);If, Ôh if abstract classes were allowed in class expressions, I could have wrote something like... Being confident of its validity at rutime.
functionfn_007(clazz: typeofMuchAbstraction): typeofMuchAbstraction{returnabstractclassextendsclazz{publictoString(): string{return`${this.valueOf()} identified as #${this.identifier}`;}}}constMyLessToNoAbstraction: typeofMuchAbstraction=fn_005(MuchAbstraction);A word to conclude : the proposal
The proposal is as follow :
Support of abstract class in class expression
Better constructor typing:
- Either by adding a core generic type. Something like what is not valid yet now:
type Constructor<T> = typeof T; (because T is used here as a value !?) - Or by constraining that constructor interfaces to build the same type, even when extending another constructor type : this means that the extending can just overload constructor by parameters.
classType{}interfaceTypeConstructor{new(): Type;}classAnotherType{}interfaceAnotherTypeConstructorextendsTypeConstructor{new(): AnotherType;//wrong: expected Type instead of AnotherType}interfaceProposedTypeConstrustorextendsTypeConstructor{new(timestamp: number): Type;// ok (at this proposal stage)}By the way, it should be considered adding a name attribute to constructor types (i.e {new()...} and its interface avatar). So as to ease the following.
functionfn<T,Cextends{new(): T}>(clazz: C){return{[clazz.name]: classextendsclazz{}}[clazz.name];}Summary
The proposals are summed as follows :
- Decorator support for
abstract class - Decorator support for abstract members (of abstract classes) with the third parameter being something like
AbstractedPropertyDescriptor or a fourth parameters as boolean (why not? let's be crazy this far) - Generate
metadata that describe up to generic types - Support of
abstract class expression - Better constructor typing:
- Either by adding a core generic type. Something like what is not valid yet now:
type Constructor<T> = typeof T; (because T is used here as a value !?) - Or by constraining that constructor interfaces build the same type, even when extending another constructor type
- Considered adding a
name attribute to constructor types (i.e {new()...} and its interface avatar).
Thanks,
Eager to hear from you all,
@SalathielGenese from Squall.IO.
TypeScript Issue-Proposal
TypeScript 2.6.2What follow are proposals for TypeScript. If follow a well know discussion, rather official #3628. It propose handling [1] abstract class decorators, abstract members decorators, generic metadata, and [2] constructor typing, and abstract class expression
[1]: Abstract class decorators, abstract members decorators, generic metadata
As to me, it is clear that by no mean should Interface exist at runtime as far as ECMA doesn't consider it worth runtime-life. However, need for this feature is more and more demanded, the reason is simple for me: more poeple are moving from JAVA to TypeScript (steping over Javascript) and so will it be with Kotlin, which they bitterly defend already: I wonder if they face the same challenging Interface DI when transpiling to Javascript.
Initial scenario
Writing such code is very difficult, not to say impossible in typescript, at least, using the official transpiler. Keeping in mind that no
interfaceshould be decorated at the moment, my suggest should make this possible :Scenario -001
In this case, generated metadata following
@CustomCrud()should describe methods and attibutes fromCrudLikewhich the abstract class implements.Scenario -002
This second senario just improves the first one, enabling us to write things like :
A word to conclude : the proposal
It come up that this would be possible :
abstract classwill support decoratorsdecoratorcould be applied to abstract members of abstract classesmetadatawill describe even generic types (If TS support complex generics, it means they can be modeled and represented, thus supported)I really wonder why such constraints were first enforced !
[2]: constructor typing, and abstract class expression
TypeScript documentation proudly exhibit this fact :
Please, stop when I am wrong. This statement also implies that the following are identical :
Scenario -001 : Constructor typing
The decorators
fn_001andfn_002where type enforced to work withTypeand are now traped to accept evenAnotherType. If one say this design is backed by no logic, I answer that typing was introduced for best practice and early violation detection. The question that springs up now is [W]hat typescript construct will ensure for sure that our constructor is forTypeandTypeonly.Scenario -002 : abstract class expression
The following scenario is a simplified image of my actual case, which is much more complicated.
Take note that even
fn_001SHOULD NOT implementidentifieraccessor as it is not its reponsibility: another decorator ensures it. As some may have seen,abstractkeyword isn't allowed in class expression which is a limitation.The following isn't a solution !
What follow works well, BUT a 'one decorator - all purpose' isn't a great design.
However, the following calls are rejected : ask me why ?
In fact, none of the above
fn_001andfn_002works when called.However, the following works great :
This somehow take us to the question in scenario -001 : constructor typing.
Nonetheless, I was able to come up with something that works :
The following implementation leads to unsuspected results : everything works well.
This encouraged me to go a little further :
If, Ôh if abstract classes were allowed in class expressions, I could have wrote something like... Being confident of its validity at rutime.
A word to conclude : the proposal
The proposal is as follow :
Support of
abstract classin class expressionBetter constructor typing:
type Constructor<T> = typeof T;(because T is used here as a value !?)By the way, it should be considered adding a
nameattribute to constructor types (i.e{new()...}and its interface avatar). So as to ease the following.Summary
The proposals are summed as follows :
abstract classAbstractedPropertyDescriptoror a fourth parameters as boolean (why not? let's be crazy this far)metadatathat describe up to generic typesabstractclass expressiontype Constructor<T> = typeof T;(because T is used here as a value !?)nameattribute to constructor types (i.e{new()...}and its interface avatar).