microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Discussion: (Reflective) Type Model

christyharagan opened this issue Β· comments

Hi all,

We currently have a AST model for TypeScript which is useful for compilers, editors, and linters.

However, it would be extremely useful to have a type model, a la Java's reflective model. There are a huge number of use-cases supporting this, but I will list a few here:

  • I have a REST interface and I define a data model for it as a TypeScript interface. I use this interface for the function definition of the REST implementation, but then use the type model to validate the input at runtime.
  • I have a service model that abstracts an interface for some system (database, or application, etc.). I define this as a TypeScript interface (maybe with some decorators as meta-data), and have a generator library that extracts this information to either generate code - or create a runtime implementation - that actually implements the service.
  • I have an injection (DI) system. I use injection decorators on class properties/constructor parameters, and use the type model to reflectively wire the correct instance.

A good example of what could be achieved with this, is something like the Spring platform for Java where our applications are built as decorated components and most of the boilerplate code is abstracted away by the platform thanks to reflection and decorators.

I have a "first stab" at implementing such a thing: typescript-schema. It's not feature complete and the design isn't finished either. But hopefully it gives a feel for what a reflective model could look like.

Is this something the TypeScript team would be interested in having as a first class citizen?

You might be interested in #2577. Reflection type functionality has been an oft requested but controversial issue for us.

Another related topic would be #3136.

I use injection decorators on class properties/constructor parameters, and use the type model to reflectively wire the correct instance.

can you elaborate on how this is done.

@danquirk So when @jonathandturner said at http://blogs.msdn.com/b/typescript/archive/2015/03/05/angular-2-0-built-on-typescript.aspx

We've also added a way to retrieve type information at runtime. When enabled, this will enable developers to do a simple type introspection.

he meant metadata generated only for decorators, not full-scale reflection (for all public symbols, for example)?

he meant metadata generated only for decorators, not full-scale reflection (for all public symbols, for example)?

correct.

What he said :)

I'm happy to see:

A few notes on metadata:

  • Type metadata uses the metadata key "design:type".
  • Parameter type metadata uses the metadata key "design:paramtypes".
  • Return type metadata uses the metadata key "design:returntype".

From #2589

What other metadata are you planing to support in the future? There are a few things that I would like to see:

  • Implemented interfaces uses the metadata key "design:implements".
  • Parameter name metadata uses the metadata key "design:paramnames".

Of course "design:implements" would require the interface names to be serialized using the interfaces names not Object (or complex type serialization). The same any case in which an interface is serialized.

Parameter name metadata uses the metadata key "design:paramnames".

this sounds interesting. and should be doable

The same any case in which an interface is serialized.

Interfaces are not serialized today. only names with a value attached to them (i.e. classes) and built-ins are serialized.

About:

Interfaces are not serialized today

I'm aware of a gist (by Ron Buckton) about complex type serialization, is that going ahead? Are you guys considering adding interface serialization support at some point?

As mentioned in my original post, I've got a project called typescript-schema that provides this kind of serialisation, as well as a reflective type model:

It has two types of model: A serialisable model, and a runtime reflective model.

It works by: AST -> Serialisable Model -> Runtime reflective model

It currently only supports 1.5, and only exported types. It's not 100% complete, but is able to process the entire TypeScript library itself, and the node type library too (as well as a number of other large libraries).

What I'd really like is to understand is:

a) if the TypeScript team would want any of this code, and if so
b) what would be required to make it ready for inclusion? I.e.: as it stands it's largely standalone (and probably doesn't follow all the code standards)

To give you an example of it at work, imagine this TypeScript code:

import {X} from 'my-module'
export interface MyInterface<T> {
  f(x: X, s: string):T
}

This will serialise to:

{
  "instanceType": {
    "typeKind": "COMPOSITE",
    "members": {
      "f": {
        "optional": false,
        "type": {
          "typeKind": "FUNCTION",
          "parameters": [{
            "name": "x",
            "type": {
              "module": "my-module"
              "name": "X"
            },
            {
              "name": "s",
              "type": {
                "typeKind": "PRIMITIVE",
                "primitiveTypeKind": "STRING"
              }
            }],
            "type": {
              "module": "@",
              "name": "T"
            }
          }
        }
      }
    }
  }
}

This can then be converted into a reflective model, which can be used like:

  let myInterfaceConstructor:InterfaceConstructor //Called an interface constructor because it has type parameters

  myInterfaceConstructor.parent // The parent module or namespace
  myInterfaceConstructor.name // "MyInterface"
  myInterfaceConstructor.typeParameters[0].name // "T"
  myInterfaceConstructor.instanceType.members['f'].type.name // "T"
  myInterfaceConstructor.instanceType.members['f'].parameters[0].name // "x"

You can then "close" that reflective interface by providing a type argument, and the output will be the corresponding interface with all type parameters correctly substituted:

  let myInterface = closeInterfaceConstructor(myInterfaceConstructor, [someType])
  myInterface.typeConstructor // myInterfaceConstructor
  myInterface.instanceType.members['f'].type // someType

To check it out, go to:

typescript-schema

and

typescript-package (which provides the code for converting a package of TypeScript into the serialisable form - including typings and nested packages via the node resolve algorithm)

When I asked for a "design:paramnames" metadata key.

@mhegazy said:

this sounds interesting. and should be doable

So I have created a new issue to request it: #4905

Interfaces are not serialized today. only names with a value attached to them (i.e. classes) and built-ins are serialized.

What if you guys will introduce new language semantic? Something like realtime interface Name {...} which will remain realtime (with empty method bodies for easing mocking in testing frameworks)?
I doubt the interface serialization will be implemented somewhen, since there is a much code written which assuming that interfaces are not realtime objects.

I forced to use classes as interfaces now to make DI work

Is this issue getting at being able to do something like the following for using interface annotations as DI tokens?

interface IAlgorithm {
    execute(): void; 
}

class FancyAlgorithm implements IAlgorithm {
    execute(): void {
        let i = 123 * 456;
    }
}

class AlgorithmExecuter {
    constructor(private algorithm: IAlgorithm) { }

    execute(): void {
        this.algorithm.execute();
    }
}

class Program {
    main() {
        // DI API based on autofac DI for .NET
        // container could be populated any number of ways just imperative for illustration
        let builder: Container = new ContainerBuilder();
        builder.RegisterType(FancyAlgorithm).As(IAlgorithm);
        builder.RegisterType(AlgorithmExecuter).AsSelf();

        // now invoke the composition root
        let executer = builder.Resolve(AlgorithmExecuter);
        executer.execute();
    }
}

The specific reason I'm asking is because when using a DI system for TypeScript that uses type annotations as metadata (such as what exists for Angular 2) this is currently not possible because no runtime "Reflect" unique metadata is emitted for interfaces. In DI circles this is basically the way DI based apps are built. It's extremely powerful and makes for very flexible and easily testable apps.

If this issue doesn't involve this kind of metadata please let me know as I'd like to create a separate proposal issue. πŸ˜„

@mikehaas763 nothing stops you today from doing the same thing but using strings in place of types. i am assuming this would be sufficient. your DI system needs to have a key to store and locate these, if you use strings, then nothing in this issue should block you.

@export("IAlgorithm")
class FancyAlgorithm implements IAlgorithm {
    execute(): void {
        let i = 123 * 456;
    }
}

@mhegazy I don't understand what you're trying to portray with that code snippet. What I would like to do is be able to register an interface type as a key in a DI container. Can you elaborate?

I don't understand what you're trying to portray with that code snippet. What I would like to do is be able to register an interface type as a key in a DI container.

I do not know much about autofac, so my comments are assuming you have a central registry that records mapping between a type and an exported entity, something a la MEF.

I am also assuming you need a key, and you want to use the name of the interface as key, all what i am saying is you can do that by making the key as a string (in this case using the name of the interface, but it should be something more unique).

the export decorator i am using refers to something like MEF ExportAttribute, in JS terms this would be something that takes a key (interface name) and a value (a class constructor), and keeps them in the list (along with arguments to the constructor possibly). that is basically will call your builder.RegisterType(FancyAlgorithm).As(IAlgorithm); except that it will be somethign like builder.RegisterType(FancyAlgorithm).As("IAlgorithm");

later on your call to builder.Resolve("IAlgorithm"); and that would return you all constructors with this key.

UPDATE: I wrote this before you replied above. Reading your response now.

@mhegazy A problem with using strings in place of types is that you lose the automatic given uniqueness of that type. Two different libraries may have an IAlgorithm interface type and both may need to be registered with a single DI container. That's easy enough to do like so if the compiler generated some sort of unique metadata token to help guarantee uniqueness and a fake import could somehow still work at runtime. So something like the following:

import IAlgorithm as ILibAAlgorithm from './libA';
import IAlgorithm as ILibBAlgorithm from './libB';
import AImplementation from './a';
import BImplementation from './b';

builder.RegisterType(AImplementation).As(ILibAAlgorithm);
builder.RegisterType(BImplementation).As(ILibBAlgorithm);

The very nature of the two IAlgorithms being two different things is enforced by them being defined separately in separate modules. I understand that this is not possible today because there is no IAlgorithm to import at runtime (or do I have this wrong?). What I'm saying is that this would be a nice feature to have in TS. I'm speaking for myself now but also for the swaths of developers that I guarantee will reiterate wanting to see the same capability as TS becomes used more.

So either I have this completely wrong and it's already possible πŸ˜„ or if not it would be awesome if we could start talking about what the actual implementation would look like and make it a formal proposal for TS or there is just better ways to do this and I'm bringing over too much cruft to TS from Java/C# etc.

UPDATE: I finished reading your reply above. Yes you can assume there is a registry (the container in autofac). I get that it would be possible by registering a type against a string key in a container but do you see the concerns I have with that around uniqueness and robustness?

I am also assuming you need a key, and you want to use the name of the interface as key

I don't want to use the string name of the interface as a key, I want to use the interface itself as a key. This is possible with concrete types because they exist at runtime. Interfaces don't exist at runtime and obviously for good reason but it would be nice to have this sort of metadata for interfaces at runtime.

One implementation that may work is to compile the interface to an empty lightweight concrete type so it can be used at runtime.

// for example
export default interface IFoo {
    foo(): void;
    bar(): void;
    lorem(): void;
    ipsum(): void;
}
// could be compiled to (depending on the target but in this case ES6)
export default class IFoo {}

That way, IFoo is a guaranteed unique key at runtime.

I think Mike's point addresses a real concern for serious application development which utilize frameworks and Dependency Injection systems. With out something like Mike's proposal in place DI systems typically work as you suggest on strings or tokenized versions of the strings. In a non TS world, that works for the most part. But in a TS/typed universe I think we should be including and utilizing types where possible in our DI systems.

@mhegazy Do you think I should start a new issue to propose/track this?

I'd like to get more people's thoughts on this. After stepping away I think the most seamless way to do this would be like I mentioned above is to compile an interface type to a "class" type. This is already done with abstract class types.

Maybe a compiler option such as --runtimeInterfaces? I realize that this in a way is a huge change because the spec is explicit that interfaces do not exist at runtime. I know that abstract classes are now supported. Am I forced as a developer using DI to now use abstract classes wherever I would have previously used just an interface?

I mentioned above is to compile an interface type to a "class" type. This is already done with abstract class types.

I am not sure i understand what you mean by "compile" to "class type". interfaces do exist in a different space (type space), and emit a value for them would cause problems when it comes to merging (see declaration merging docs for more information).

I want to use the interface itself as a key. This is possible with concrete types because they exist at runtime.

one thing to note is TypeScript's type system is structural, it is not nominal (like other languages such as C# and Java with DI systems). so the interface name, or declaration is of no consequence here; consider an example with two different classes looking for interfaces with different names, but comparable structures, do you want your DI to find them or not?

interface Person {
     name: string;
     title?: string;
}

interface SomethingWithAName {
    name: string;
}

class C {
     constructor(c: SomethingWithAName) {}
}

var p: Person;
new C(p); // this is fine, the two interfaces are valid

then what would you do with things that do not have a name?

class C {
     constructor(a: {name: string}) {}
}

or type aliases?

type myType = string;
class C {
     constructor(a: myType) {}
}

or more complex type operators:

type myType = string;
class C {
     constructor(a: (string | { name: string }) & EventTarget) {}
}

Obviously a typical DI system from a language with nominal type system like C# would not fit here with no compromises. i would say you will have to limit the set of supported language constructs to allow your DI system to work, i.e. say only classes are supported by this system, interfaces, structural types, etc will not be allowed.

If you agree with this proposition, then it should just work with classes. classes have values, and can be used at rumtime as keys to your DI system. and that should be easy to model today using decorators, e.g.:

    // use an abstract class instead of an interface
    abstract class Algorithm {
        abstract execute(): void;
    }

    @exportAs(Algorithm)   // use the class constructor as a key for your type lookups
    class FancyAlgorithm implements Algorithm { // use implements here, so no runtime changes (i.e no calls to __extend)
        execute(): void {
            let i = 123 * 456;
        }
    }

    abstract class Class { }

    function exportAs<T extends Class>(typeObject: T) {
        return function (target: T): void {
            // wire the export
            builder.RegisterType(target).As(typeObject);
        }
    }

    // later at runtime you would do:
    let executer = builder.Resolve(Algorithm); // should find FancyAlgorithm

Obviously a typical DI system from a language with nominal type system like C# would not fit here with no compromises. i would say you will have to limit the set of supported language constructs to allow your DI system to work, i.e. say only classes are supported by this system, interfaces, structural types, etc will not be allowed.
If you agree with this proposition, then it should just work with classes. classes have values, and can be used at rumtime as keys to your DI system. and that should be easy to model today using decorators, e.g.:

Using abstract classes as interfaces approach works not very well for DI. Any abstract methods will be omitted from compiled JS, so it's not possible to create tests stubs from abstracted classes (for ex. using sinon.createStubInstance()). Also the compiler doesn't emit any runtime checks to prevent creation abstract classes at runtime. If you forgot to bind implementation to abstract class acting as interface, it will create empty object of your interface instance, instead giving you error:

abstract class TestInterface { ... }

class TestController { 
   @Inject
   public myService: TestInterface;
}

// Forgot to bind any implementation to TestInterface
let controller = container.resolve(TestController); // Doesn't throw any error since abstract class could be created at runtime.

I forced to use now these ugly constructions:

class Algorithm {
    constructor() {throw new Error();}
    public execute(): void {}
    public calculate(): number {return}
}

class MyAlgorithm implements Algorithm { .... }

With this, runtime checking and mocking methods (like sinon.createStubInstance()) will work correctly.

As an FYI, I was just suggesting compiling an interface to a type as one specific way to solve the problem of providing this sort of interface metadata. It could be handled other ways.

interfaces do exist in a different space (type space)

I'm aware. I suggested this purely as a means to be able to use interfaces in a meta way at runtime by just compiling it to a value type. I'm not saying it's the only way or even the proper way.

and emit a value for them would cause problems when it comes to merging

Why would this cause issues with merging? Merging does occur when an interface is defined in separate modules does it?

I'm aware that the type system is structural which I admit makes it harder for me to reason about this.

then what would you do with things that do not have a name?

If I was declaring the type annotation of something with a literal, than I wouldn't expect to be able to use that as a key in my DI system.

If you agree with this proposition, then it should just work with classes. classes have values, and can be used at rumtime as keys to your DI system

The problem with depending on classes is that it blatantly violates the dependency inversion principle: "Depend upon Abstractions. Do not depend upon concretions.". I can see people saying "see, just use an abstract class for that". Well, if I just end up using an abstract class just like an interface, what is the point of having interfaces in TS?

At the end of the day this conversation is just about possibilities to provide a means to an end and allowing something that makes development better and easier. That end being I want to be able to continue to program like the following snippet but also have dependencies injected as expected based on the type annotation that is already specified (IDependency).

class Foo {
    constructor(dependency: IDependency) {}
}

In the meantime I had planned on just using abstract classes as interfaces (like in the following snippet), but will have to look more closely at the problems that introduces that @asvetliakov mentioned above.

abstract class IAlgorithm {
    abstract execute(): void;
}

Ideally it will be cool if you guys implement something like that:

interface realtime MyInterface {
    public method1(): void;
    public method2(): string;
}

Which will be compiled to:

function MyInterface() {
    throw new Error("Not possible to create MyInterface instance");
}

MyInterface.prototype.method1 = function () { throw new Error("Not possible to call interface method MyInterface::method1"); }
MyInterface.prototype.method2 = function() { throw new Error("Not possible to call interface method MyInterface::method2"); }

This will not interfere with existing interfaces in type scope and people will be able to use DI as it was intended.
Or at least provide runtime constructor checking & empty function stubs for abstract classes & methods

@asvetliakov I like where you're going with that but at the same time IDK if I like that it has to be explicitly marked. If I'm implementing an interface that some 3rd party lib has provided, then I don't have the ability to mark that interface.

@mikehaas763 Don't see any issues. 3rd party libs are usually declaring variable (with interface type) if exporting something, so there will be runtime information and you can use this variable as key for DI.

@asvetliakov I mean them declaring an interface to use as in the concept of inversion of control. They define an interface that a consumer implements and passed back into their lib.

Why would this cause issues with merging? Merging does occur when an interface is defined in separate modules does it?

now you are talking about limiting it to only module scopes. that was not in the OP.

Well, if I just end up using an abstract class just like an interface, what is the point of having interfaces in TS?

interfaces in TypeScript are design-only constructs, just like the rest of the types. using these constructs in the value space is not a TS design goal.

talking with @rbuckton today, here is another option that works today and does not use classes:

   const IAlgorithm = new Symbol("IAlgorithm"); // unique identifier for the interface
   interface IAlgorithm {
       execute(): void;
    }

    @exportAs(IAlgorithm)   // the interface and the var are merged
    class FancyAlgorithm implements Algorithm { 
        execute(): void {
            let i = 123 * 456;
        }
    }

@mhegazy Your approach is good because you only have one magic string:

declare class Symbol {
    constructor(a: string);
};

function bind<T>(a : Symbol, b : T) {}
function resolve<T>(a : Symbol) {}

function inject(...types : Symbol[]){
    return function(target : any) {

    }
}

const IFoo = new Symbol("IFoo");           // The only magic string in the whole process

interface IFoo {}
class Foo implements IFoo {}

@inject(IFoo)                              // No more magic strings when declaring injections
class Test {
    constructor(algorithm : IFoo) {

    }
}

bind<IFoo>(IFoo, Foo);                     // No more magic strings when declaring bindings
var foo = resolve<IFoo>(IFoo);             // No more magic strings when resolving a dependency

I think that we need to come up with some way to enable this without having to do extra work. When I say extra work I mean declaring magic strings or symbols. This are just "manual metadata hacks" metadata should be generated by the compiler.

I don't know if it is the best way but we could use a new compiler option emitInterfaceSymbols.

When the emitInterfaceSymbols flag is used and we declare an interface:

interface IFoo{}

The symbols are generated by the compiler:

const IFoo= new Symbol("IFoo");

At design time we would need to use declare var IFoo : Symbol; to avoid compilation errors:

// the run-time symbol is generated by the compiler but we need to declare at design-time
// is extra work but at least it is not a magic string...
declare var IFoo : Symbol; 

declare class Symbol {
    constructor(a: string);
};

function bind<T>(a : Symbol, b : T) {}
function resolve<T>(a : Symbol) {}

function inject(...types : Symbol[]){
    return function(target : any) {
        // ...
    }
} 

interface IFoo {}
class Foo implements IFoo {}

@inject(IFoo)
class Test {
    constructor(algorithm : IFoo) {

    }
}

bind<IFoo>(IFoo, Foo);
var foo = resolve<IFoo>(IFoo);

It would be cool if the IDE is able to identify when we are declaring an interface and using the emitInterfaceSymbols flag and it would automatically assume that the a symbol with he same name as the interface is declared. So we don't need to type the line below to avoid compilation errors:

declare var IFoo : Symbol;

Hi everybody, I would just add my 2 cents to the discussion. Some time ago I started working on a type serializer that is based on rbuckton's proposal types.d.ts; currently it's in a prototyping stage and not published yet, but I have uploaded some sample code in order to discuss a possible way of implementing full type serialization. I know, there could be many drawbacks when doing this, but in some cases could be useful to have all information about implemented interfaces, generic types and so on. I have included some open points in the readme, I would be glad if you took a look at it here.

Ok, this discussion is definitly interresting, and I will openly admit that I'm no expert on this, yet.

However, reading through it all, it seems like the thing most people are asking for is actually something very simple - just compile interfaces to an empty constructor function, so we have something at runtime we can use as a key when doing dependency injection. Thats it. No theoretical discussion of type systems and stuff there. We just need that key.

To me, this sounds like something that should be fairly simple to implement, and it would be consistent with how abstract classes are currently compiled. Abstract classes do already exist at runtime, and we just need interfaces to be compiled in exactly the same way. I really don't see the difference here, and the DI problem everyone is talking about is very real - when building real-world apps, we absolutely need the ability to use interfaces as keys for DI.

I think it would be a good idea to track this in a separate issue, and then deal with the the whole question of reflection and type model separately, as it is really a completely different issue. I therefore suggest that #3060 is reopened, as it represents exactly what is being asked for here.

Unless of course I'm missing the point, and there is a good reason why this is not technically possible?
I really see no reason why we should have to deal with magic strings anywhere to acheive this.

However, reading through it all, it seems like the thing most people are asking for is actually something very simple - just compile interfaces to an empty constructor function

So just write your interfaces as classes. that should be a simple regexp.

So just write your interfaces as classes. that should be a simple regexp.

Then what's the point of having interfaces in TS in the first place? Why not just stop using interfaces in your codebase, deprecate the construct from the lang, and move on?

However, it is what I'm doing now. I think many people (including myself earlier on) didn't realize that you can implement constructs other than interfaces like class Foo implements AbstractOrConcreteType{}. A stringent code review process can help ensure that no implementations are allowed into abstract classes and we just treat the abstract classes purely as an interface. It does still come back to the idea of why even have interfaces in TS going forward?

Then what's the point of having interfaces in TS in the first place? Why not just stop using interfaces in your codebase, deprecate the construct from the lang, and move on?

interfaces, as well as type aliases, do not exist at run-time. They are merely a design time construct. this is the whole point of TypeScript :) a design time tool to allow you to make sense of your code with no runtime cost.

Classes, variables, etc.. are runtime constructs by definition. So I am confused why you want interfaces to have a runtime manifestation, but at the same time do not use classes.

If you want a specific use case for this, the dependency injection in the Aurelia framework is a very good example. This works today if IEngine is an abstract class, and would work for interfaces too, if only the interface existed at runtime.

var container = new Container(); 
container.registerSingleton(IEngine, Engine); 

@autoInject()
export class Car {
    constructor(public engine: IEngine) {
    }
}

The problem is, I can't just replace my interfaces with abstract classes, as that would prevent my implementation from inheriting from anything other than the interface. Maybe I want my Engine to inherit from CarPart or something - interfaces are about composition, not inheritance.

Is there any technical reason why interfaces cannot be compiled in the same way abstract classes are?
Of course, in the IDE the two things are quite different, but we just need the same output in JS.

interfaces, as well as type aliases, do not exist at run-time. They are merely a design time construct. this is the whole point of TypeScript :) a design time tool to allow you to make sense of your code with no runtime cost.

I know this and you've reiterated it to me several times. The point comes down to DI. If I can't use interfaces for pragmatic use cases such as with a DI framework, how much value do they really add. A developer (the consumer) should not be thinking about whether certain constructs are available at runtime or not when they are just trying to get work done.

I absolutely agree.

@mhegazy If an abstract class has a runtime manifistation, why should it be any different for an interface? I realize typescript is considered a typed superset of ecma script, but interfaces and DI is absolutely essential to building real world apps, and I see no reason why typescript should not make this possible.

interfaces are about composition, not inheritance.

I see your point. But generally interfaces are about enabling one to compose, but really anything that implements an interface etc does become a sub type of it. Therefore it's kind of inheritance too. πŸ˜„

The problem is, I can't just replace my interfaces with abstract classes, as that would prevent my implementation from inheriting from anything other than the interface.

I'm not following why it's not technically possible?

abstract class Foo
{
    void DoFoo();
}

class Bar implements Foo
{
    void DoFoo() {}
}

someFunction(fooer : Foo){}
someFunction(new Bar());

Is there any technical reason why interfaces cannot be compiled in the same way abstract classes are?

Not technical reasons but it's a change to the spec and I think historically the reason has been about not shipping extra cruft to the browser.

Hi guys, I would just to point up that this kind of things IS already feasible (I did it, just I said previously), but it may be better if the compiler would be a bit more "extensible", so I should not have to wire up things in the compiler in order to emit interfaces, classes and other metadata; we could simply recall a compiler plugin to do this job. The main issue is not the extraction of metadata (I'm using the plain compiler API), but its "linking" to the emitted code.

@mikehaas763: abstract classes won't cover the case of mixin interfaces. A Dependency Injection mechanism could be driven by more interfaces, and still injecting the same singleton object, just because it implements more than one interface.

Hmmm, ok, I think I might have missed the point that a class can implement other classes like that - that's really interresting, and it does in fact make what I'm talking about possible using abstract classes.

However, it does feel extremely strange to use an abstract class as if it was an interface - and the fact that it is possible to e.g. define actual methods, and not just method signatures, in an abstract class, that just feels plain wrong when what I'm really trying to do, is to define a contract the class must implement.

While I guess abstract classes can be used as a workaround like this, I still think interfaces should have a runtime manifistation, and I'm pretty sure that's what 99% of developers expect when they start using typescript. Having a class implement an interface makes the intent much clearer, compared to abusing abstract classes as if they were interfaces (i.e. contracts with no behavior), and then having code reviews to avoid getting method bodies and other mistakes in there.

I really think you guys should reconsider this, and emit code for interfaces too - again, I don't see why it should be any different from abstract classes. It would just mean that interfaces would work the way pretty much everyone expects them to - otherwise I really see no value in the interface construct at all.

I am firmly of the belief that any DI system for JavaScript is better served by using unique exported string or symbol values. I've been thinking more and more about adding special compiler-specific decorators that could target things like interfaces and add metadata to the classes that implement them.

Just to clarify, I don't really have a strong preference as to whether an interface is emitted as a constructor function or any other unique value - I just used the abstract class as an example due to the similarity between the two, but I guess emitting a Symbol might technically be more correct, as what we're talking about here is an identity and not something that should ever be instantiated - but really, anything that can be used as a unique key for DI would make me a very happy developer :-)

@remojansen, the solution you suggested, where setting an emitInterfaceSymbols option will cause symbols to be emitted for interfaces, that sounds a lot like the solution I would like to see - as long as it doesn't require any code changes :-)

Just a quick note here - isn't the question of whether something should be emitted to represent an interface very similar to the case of const enum declarations?

Normally a const enum declaration would be compiled away, but here we have the --preserveConstEnums options to ensure it is preserved in the generated code.
https://github.com/Microsoft/TypeScript/wiki/Compiler-Options

To me, this appears to be a very similar issues... maybe a preserveInterfaces option could be added, so we can reference an interface just as if it was a class, thus enabling the DI scenario discussed here?

Just like code referencing a const enum may appear to work while coding, and then breaks at runtime as described in https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#94-constant-enum-declarations, the code in my comment #3628 (comment) compiles just fine and looks like it should work as expected - but then it all blows up at runtime due to the missing runtime representation of the interface. This is a highly frustrating and unexpected experience, especially for developers new to typescript, and I still believe this is a very legitimate use case for interfaces and therefore something that should be enabled.

@rbuckton I think most of us agree that using string (rather than complex type serialization) is the right balance between what we want to achieve and run-time overhead. In my opinion runtime type assertion is a bad idea.

I saw your comment about interface names not being enough information. This causes the following issue in my project:

interface IA {
  x : number;
}

interface IB {
  x : number;
}

class A implements IA {
  x : number;
}

class B implements IB {
  x : number;
}

kernel.bind(new Binding<IA>("IA", A));
kernel.bind(new Binding<IA>("IA", B)); // OK because structural typing

I personally don't mins this issue because:

  • It doesn't violates structural typing
  • It also respects the Liskov substitution principle (so we don't violate the SOLID principles).

At the end of the day what really matters when injecting a dependency is its interface not its name.

I'm not planing to do auto-wiring so I can live without 100% identifiable interfaces.

At the moment developers need to manually type the names of the interfaces to be injected:

@Inject("FooInterface", "BarInterface")
class FooBar implements FooBarInterface {
  public foo : FooInterface;
  public bar : BarInterface;
  public log(){
    console.log("foobar");
  }
  constructor(foo : FooInterface, bar : BarInterface) {
    this.foo = foo;
    this.bar = bar;
  }
}

Magic strings are error-prompt and not refactoring-friendly. It would be great if we could leave the compiler add those strings to the metadata using:

Reflect.getMetadata("design:paramtypes", FooBar); 

It should return ["FooInterface","BarInterface"] so users can forget about magic strings.

Note that we need actual strings not functions named like the interfaces because we need to be able to use JS compressors.

I don't think using Symbols is a good idea because I think it could create some problems:

var f2 = Symbol("FooInterface");
var f = Symbol("FooInterface");
f == f2 // false

Specially if we are consuming third party code with metadata.

The problem with Reflect.getMetadata returning an array of strings is the possibility of collisions. Consider this:

// a.ts
export interface Service { x: number; }
// b.ts
export interface Service { y: number; }
// main.ts
import * as a from "a";
import * as b from "b";
class ServicesImpl implements a.Service, b.Service {
}
Reflect.getMetadata("design:paramtypes", ServicesImpl); // ["Service", "Service"]

Strings are useful if they are unique, and there's no reliable way for TypeScript to generate a unique string that doesn't change per build. It becomes a refactoring and code reorganization hazard.

A better approach would be this:

// a.ts
export interface Service { x: number; }
export const ServiceName = "urn:a:Service";
// b.ts
export interface Service { y: number; }
export const ServiceName = "urn:b:Service";
// main.ts
import * as a from "a";
import * as b from "b";
import { provide, getService } from "some-composition-framework";
@provide(a.ServiceName)
@provide(b.ServiceName)
class ServicesImpl implements a.Service, b.Service {
}

const aService = getService<a.Service>(a.ServiceName);
const bService = getService<b.Service>(b.ServiceName);

Symbols are event better, as they can guarantee uniqueness:

// a.ts
export interface Service { x: number; }
export const ServiceKey = Symbol("Service");
// b.ts
export interface Service { y: number; }
export const ServiceKey= Symbol("Service");
// main.ts
import * as a from "a";
import * as b from "b";
import { provide, getService } from "some-composition-framework";
@provide(a.ServiceKey)
@provide(b.ServiceKey)
class ServicesImpl implements a.Service, b.Service {
}

const aService = getService<a.Service>(a.ServiceKey);
const bService = getService<b.Service>(b.ServiceKey);

The problem with Reflect.getMetadata returning an array of strings is the possibility of collisions.

I don't know if this option has already been discussed, but... What about a fully qualified name? the tsconfig.json (in the "root package") already contains the list of each ts file, which could be used as the "base name" for interfaces.

Example:

// common/baz/StringUtils.ts
export interface Service { x: number; }

// foo/bar/AnotherService.ts
export interface Service { y: string; }

// Test.ts
import * as a from "common/baz/StringUtils";
import * as b from "foo/bar/AnotherService";

class ServicesImpl implements a.Service, b.Service {
}
Reflect.getMetadata("design:paramtypes", ServicesImpl); // ["common.baz.StringUtils#Service", "foo.bar.AnotherService#Service"]

Maybe this could be better than nothing :)

EDIT:
the same strategy may be used for modules, for example:

// common/baz/StringUtils.ts
export module foo {
    export interface Service { x: number; }
}

// emitted as: "common.baz.StringUtils#foo.Service"

If someone is still interested in reflection capabilities, I just released an enhanced version of the compiler that emits classes/interfaces metadata for runtime type checking. The compiler injects some synthetic instructions in the AST just after the parsing phase, so the metadata is available also for intellisense/autocompletion. I haven't tested this with Visual Studio, but with Atom it works really well. Keep an eye on this.

@pcan great work πŸ‘ we really need better reflection in TypeScript 😒 I hope to see something in the official compiler soon. We could create so many amazing tools.

I just implemented a proof-of-concept for using decorators to define Models in sequelize:
https://github.com/felixfbecker/sequelize-decorators

If you don't know, Sequelize is the most popular ORM for NodeJS. In version 4 we are enabling users to use classes to define their models. This is great for TypeScript, because we can use decorators to define the attribute metadata, like what column type it should be, if it should allow NULL, if it should be a primary key, if it should have a unique constraint etc etc.

I actually implemented it in a way that you can leave out the type for the types that are already emitted under design:type. So if the property is a Date, you don't need to pass to the @Attribute decorator that the sequelize / database data type should be DATE, it is inferred. Similarly, Buffer is inferred as BLOB. But it is very limited atm, and I see so much more potential. For example:

  • if it is a union type of Date | null (TS 2.0), we could automatically allow NULL, else set a NOT NULL constraint. Currently this will just become Object. We need union type info.
  • if it is an array, design:types will currently simply be Array. But a database needs to know what type of array it can be, so we need generic type info. The type would follow the same inference rules described here.
  • if the type is an enum or a union type of string literals (or a type alias to such), we could infer an ENUM, with the possible values. This would be really awesome because currently you have duplicate the possible values for the type and for the model definition.
  • if it is an object hash with a key signature like {[key: string]: string}, or a Map, we could infer HSTORE as a type
  • if it is a BigNumber, we could infer DECIMAL
  • if it has a type of Sequelize.Range<SubType> (which is an interface provided by the type declarations), it would be inferred as a RANGE of the declared subtype, with subtype following the same inference rules outlined here.
  • if it is Object or any interface, we could infer JSON.
  • if it is an interface compatible with GeoJSON, we could infer GEOGRAPHY.

Basically, it should be possible to get all the info TypeScript has about a property. Which leads me to the thought, by can't TypeScript simply expose the AST node? I'm sure it already has structures that contain all the necessary information.

Hi guys! { structuralTypes: false } in tsconfig.json is never gonna happen right? 😒

I'd propose a special decorator on interfaces

export const MyInterface = Symbol("MyInterface")

@reflectAs(MyInterface)               // here MyInterface is the constant
export interface MyInterface {        // that will be emmited as a reflected type 
      a: number;
      b: boolean;
}

It would be extremely simple and efficient solution for DI

@Artazor
The problem is interfaces are not compiled to any real js code, but decorators are. What should be the output of code you suggested?

I think, that's the reason why it should be keyword, not decorator.

@Artazor @koroandr I think the best solution is to add a compiler option, something like the current --emitDecoratorMetadata but --emitInterfaceMetadata or something similar but I also think that --nominalTyping which would disable structural typing. I assume this is quite complicated but I'm not a compiler guy so I'm not sure about it...

I know that the structural type system was designed to facilitate interoperability with JavaScript libraries but disabling structural typing would enable some awesome tools just like the strict null checks mode does.

@remojansen I believe that may cause some overhead, because in fact you wouldn't need reflection metadata for every interface you create, just for injectable ones (in case of DI), so that will produce a lot of wasteful code when compiled to js. That's why I suggested to use some keyword, like so:

export reflexed interface MyInterface {
    a: number;
    b: boolean;
}

Can we please just have an --emitInterfaces option and be done with this?
This discussion has been ongoing for so long now, and it really should not be that complicated. There is a crystal clear and highly desirable use case for this, and we just need the compiler to emit something that we can reference at runtime - and I seriously doubt this would cause any measurable overhead, so no need to complicate things with new keywords or decorators.

So when I write something like this in foo.ts:

export interface Foo {}

The compiler could just emit code similar to what would be emittted if it was written like this:

export abstract class Foo {}

(but of course, it is still an interface, and therefore still can't be used with instanceof, etc.)

That way, when I do something like this in bar.ts, the metadata emitted for the foo constructor parameter would reference the imported Foo - instead of always referencing Object as it currently does.

import {Foo} from "./foo";

@autoinject
class
{
    constructor(foo: Foo) {}
}

Done, problem solved - The metadata now contains a reference to something representing the imported interface, which can then be used as a key for dependency injection. Whether that something is then an Symbol, Object, or Function really doesn't matter - it's a reference, and that's all that matters.

I'd love to see this happening too. I find it essential for evolving the language and creating better software.

I understand that metadata supports only names with a value attached to them (i.e. classes) and builtins. Is it prohibitively difficult to extend support to native objects like Date?

Can we please just have an --emitInterfaces option and be done with this?

This would be problematic in that it would change the meaning of existing code. Once you start reifying interfaces arbitrarily you will get variable collisions and variable shadowing in all sorts of unexpected places. Even referencing lib.d.ts would cause code to fail.

interface Element {
}

takes on a whole different meaning.

Similar idea: #12605

Based on my own scenario and what I'm seeing here, it seems like the commonality is that this functionality is only ever desired for classes and interfaces.

A few suggestions and caveats have surfaced in this discussion, but a way forward seems apparent as I read:

  • Compiler flag to auto generate metadata. The cognitive overhead and grossness of having to manually tag types for reflection nullifies a significant portion of any convenience a library that uses them can offer. I get that it's a "big thing", but at least give us the option.
  • Because TypeScript is structural, metadata identifiers should be generated from the signatures of types, not their names.
  • If it's really an issue, only auto generate metadata for classes and interfaces. Again, it seems like every use case just wants to inject the quasi-statically-typed stuff.

Two years for a feature that's generated an awful lot of chatter. I would file this ticket under "more impactful than it's being given credit for".

inversify/InversifyJS#534

✏️

We also had a problem with DI and interfaces and made up two sollutions.

  1. Using interface as a var
/// ISomeService.ts
export interface ISomeService {}
// Now ISomeService is also an empty object emmited as ISomeService = {};
export module ISomeService { let ISomeService: void; }

/// SomeService1.ts
import { ISomeService } from "./ISomeService";

// And could be used as a var
@Injectable(ISomeService)
export class SomeService1 implements ISomeService {}
  1. Decorating an interface
/// ISomeDBType.ts
export class ISomeDBType
{
    // Since ISomeDBType is a class we could apply decorators to its properties,
    // but TS will emmit empty function/class.
    @PrimaryKey
    public ID: number;
}

/// ISomeValidationType.ts
export class ISomeValidationType
{
    @Validate("number")
    public ID: number;
}

/// SomeType.ts
import { ISomeDBType } from "./ISomeDBType";
import { ISomeValidationType } from "./ISomeValidationType";

// We also have a special "partial" decorator which copies all metadata from ISomeDBType and
// ISomeValidationType to SomeType and lets us keep classes clean (since we heavily rely on
// metadata for almost everything).
@Partial(ISomeDBType)
@Partial(ISomeValidationType)
// TS can use class as an interface, so everything is fine.
class SomeType implements ISomeDBType, ISomeValidationType
{
    private id: number;
    public get ID(): number{ return this.id; }
    public set ID(ID: number) { this.id = ID; }
}

For those who interested:
I wrote little library, which uses customTranformers api. It emits some basic classes and props metadata, including generic arguments and union types.
Looks like typescript team doesn't want to include such functionality into the main repository (at least for now).
But same can be achieved using transformers api
So, this is proof of concept, comments are welcome.
https://github.com/goloveychuk/tsruntime
linked with #3015

I agree that custom transformers can be a solution.

The following are the examples of what you can do with custom transformers.

import { enumerate } from 'ts-transformer-enumerate';

type Colors = 'green' | 'yellow' | 'red';
const Colors = enumerate<Colors>();

console.log(Colors.green); // 'green'
console.log(Colors.yellow); // 'yellow'
console.log(Colors.red); // 'red'
  • ts-transformer-keys, which reads definition of interface to obtain its keys as an array of strings.
import { keys } from 'ts-transformer-keys';

interface Props {
  id: string;
  name: string;
  age: number;
}
const keysOfProps = keys<Props>();

console.log(keysOfProps); // ['id', 'name', 'age']

Can smbd help me?
https://github.com/goloveychuk/tsruntime/blob/master/src/transformer.ts#L58
There I'm emitting reference type. But if this type was imported from another module, and not used anywhere on this file, typescript removing it (as unused). So I need somehow mark this type as used. How can I do this? What mechanisms checking this? ImportTracker? codefixes/unusedIdentifierFixes?

upd: smth found, looks like it's isReferencedAliasDeclaration

Yes. Import elision is handled in the checker. isReferencedAliasDeclaration pulls information from the checker to indicate whether the import was referenced.

Just some thoughts. If typescript team don't going to make enhanced metadata emitting inside typescript codebase, it would be great boost (for custom libraries) if you'll move metadata logic to the external library, which uses only public api. It will help to understood, what api is needed and currently not available.
And it will really help people to write solid custom library for metadata emitting.
E.g. for now
https://github.com/Microsoft/TypeScript/blob/2150a7796b0dfe83c5286755410e0fd1646ed86c/src/compiler/transformers/ts.ts#L1806
EmitResolver api is not available.
Thanks.

Is already there any solution for emitting better metadata? With better I mean metadata with correct complex types or metadata about interfaces (which are known to disappear after transpiling).

@goloveychuk Well, as I've seen no solution for what I want to achieve I've made two libraries:
The first one generates a TypeScript file including some metadata, just like tsruntime but without the need of placing decorators. It also aims to emit interface metadata in the near future.
https://github.com/lilezek/awesome-metadata

The second one uses generated TypeScript metadata to implement the functionality that GSON offers in Java, for serializing and deserializing classes.
https://github.com/lilezek/tson

I hope someone finds these projects useful.

Almost 3 years passed and there is no kind of 'official' statement from Typescript team about this issue.

First-class Reflective features would unlock many possibilities as described in issue description.

Many third-party libraries have been created and they're doing good job, but in fact, it's dividing the way things can be achieved with typescript. First class support would avoid having several standards of doing something.

As this issue is important, such standards will eventually emerge if there will be void in this area. I consider it bad thing because community fragmented by the way of doing something important would slow down development and stability of language in real world scenarios.

Also I think that even if Typescript team have no plans for implementing it or finds it unnecessary or low-priori, it'd be valuable to have some information about it.

Thanks πŸ‘

I'm pretty sure that typescript team will not implement reflection support.
#21479 (comment)
Even if they implement more powerful (then now we have) reflection for classes (which have runtime representation),
they will not make this for interfaces (which some developers want).
But it's not a big deal, since it could easilly be imlemented via plugins.
The only thing is needed from Typescript team is to give us an option to have in `tsconfig.json'

{
    plugins: ["awesomePlugin"]
}

and in 'awesomePlugin.ts'

export default {
      initialize(program: Program) {}
      emit(){}
}

Which is pretty similar to customTransformers api, but with access to Program and with ease installation (no mess with typescript loaders configs).
And this, looking at ts source code, pretty easy to implement.

Many third-party libraries have been created and they're doing good job, but in fact, it's dividing the way things can be achieved with typescript. First class support would avoid having several standards of doing something.

As this issue is important, such standards will eventually emerge if there will be void in this area. I consider it bad thing.

I completely agree with you. The most dangerous thing is not the lack of reflection, but the fragmentation of the community-built solutions.

they will not make this for interfaces (which some developers want).

I did this two years ago in my reflec-ts fork of the official compiler, and with a very low effort, too. I repeat: we just need a standard track for this topic, otherwise in the future many of the libraries/frameworks produced by the community will be very fragmented and incompatibilities will raise, consequently.

I'd be happy to write this code by hand...

const IFoo = Symbol('IFoo');
interface IFoo {
  bar();
}

If using emitDecoratorMetadata in this scenario...

export class Baz {
  constructor(foo: IFoo) {}
}

...actually generated design metadata with the IFoo symbol value in it, rather than Object. To me, this seems like a bug, since everywhere else, the compiler seems to understand when to use the interface type vs. when to use the constant. But when it emits design data, it does it incorrectly.

As an FYI, this is what we actually do today:

export const IFoo = DI.createInterface('IFoo');
export interface IFoo {
  ...
}

export class Bar {
  constructor(@IFoo foo: IFoo) { ... }
}

Our createInterface helper creates a decorator that will push itself into the metadata so it can be used as a key by the DI. This is a workaround that is needed because of the emit issue I described above. I'd prefer to just write this...

@autoinject
export class Bar {
  constructor(foo: IFoo) { ... }
}

(Would be better to not need the autoinject decorator to turn metadata emit on, but I'd be happy to just see the interface/value emit fixed.)

While this fix wouldn't satisfy every need on this thread, it's a reasonable non-breaking change that would open up the DI scenarios a little more.

Interfaces should have been called Shapes, that would have prevented a lot of confusion and kept room for actual interfaces known from other languages.

Maybe its worth introducing a nominal typing variant of interface? Possible name could be contract or, to avoid the confusion with other languages (read: interfaces are shapes and contracts are interfaces), a subqualifier for interfaces: nominal interface, runtime interface

When using Symbols for runtime transpilation then the code representing the interface at runtime must be moved to a separate module to avoid side effects (because importing only an interface from another would result in this import being removed when transpiled).

A possible way to represent interfaces at runtime:

/** INTERFACE RUNTIME */
const Implements = Symbol('Typescript Interface Implementations');

function Interface(desc?: string) {
    const symbol = Symbol(desc);
    const instance = function() {} as any;
    Object.setPrototypeOf(instance, Interface.prototype);
    Object.defineProperty(instance, Symbol.hasInstance, {
        value: (other: any) => {
            if (!other.constructor || !other.constructor[Implements]) {
                return false;
            }

            return other.constructor[Implements].includes(instance);
        }
    });
    Object.defineProperty(instance, Symbol.toPrimitive, { value: () => symbol });
    Object.defineProperty(instance, 'toString', { value: () => desc || '' });
    return instance;
};
Interface.prototype = Object.create(Function.prototype);
/** INTERFACE RUNTIME */

const MyInterface = Interface('b::MyInterface'); // added by transpilation
interface MyInterface {
}

class Other implements MyInterface {
    static readonly [Implements] = [MyInterface]; // added by transpilation
}

const implementor = new Other();
const bindings = {
    [MyInterface]: implementor,
}

console.log(MyInterface); // [Function: instance]
console.log(typeof MyInterface); // function
console.log(MyInterface instanceof Interface); // true
console.log(MyInterface instanceof Function); // true
console.log(MyInterface instanceof Object); // true
console.log(MyInterface instanceof Symbol); // false
console.log(MyInterface.toString()); // b::MyInterface
// console.log(MyInterface + ''); // Error
console.log(implementor instanceof Other); // true
console.log(implementor instanceof MyInterface); // true
console.log(bindings); // { [Symbol(b::MyInterface)]: Other {} }

Object.getOwnPropertySymbols(bindings).forEach((k) => {
    console.log(k); // Symbol(b::MyInterface)
    console.log(k == MyInterface); // true
    console.log(MyInterface == k); // true
    console.log(k === MyInterface); // false ! some sadness, but we can live with that
})

Any news about official support from the TS team?

Maybe it can help someone, I found this lib (https://github.com/dsherret/ts-simple-ast), looks very promising for compile-time reflection

I've been playing around with custom transformers in trying to solve the problem with dependency injection and interfaces. I've been able to make a custom transformer and a simple dependency injection library to demonstrate the capabilities of my transformer. The transformer is able to do the following:

  • Transform an interface into a unique symbol when calling InterfaceSymbol<MyInterface>(). It will always return the same Symbol for the same interface (so far in my testing).
  • Get the parameters of the constructor when binding a newable to an interface. If a parameter is an interface it will undergo the same transformation as calling it with InterfaceSymbol<T>().

Example of how to use it:

interface IBird {
  canFly(): boolean;
}

class Crow implements IBird {
  public canFly() {
    return true;
  }
}

class App {
  constructor(bird: IBird) {
    if (bird.canFly()) {
      console.log("Bird can fly");
    } else {
      console.log("Bird can't fly");
    }
  }
}

const container = new Container();
container.bind(InterfaceSymbol<IBird>(), Crow);

container.resolve(App);

If you're interested in it you can go to https://github.com/YePpHa/ts-di-transformer for more information. Please note that it's mostly a proof-of-concept even though it's kind of working.

I think that custom transformers are the solution for this issue. It might be possible that there's other possible solutions. But so far I think they're the way to go.

The only hope is this package. https://www.npmjs.com/package/reflect-metadata

Up!
This would definitely profide a clean solution to parse/validate untrusted JSON input at runtime!

This would definitely profide a clean solution to parse/validate untrusted JSON input at runtime!

I suggest to try the TypeOnly checker.
https://github.com/tomko-team/typeonly-checker

I have been playing with this idea for a few day and published a quick POC using a transformer.
https://github.com/aenario/tsmirror. If you are interested, feel free to open an issue there to describe your usecase.

For the "REST interface" use case, I've worked on a library I hope can solve it conveniently (requires a Babel setup at the moment):
https://github.com/edbentley/ts-validate-type

Hasn't anyone used Pascal? haha .. In Object Pascal there'd be a compiler flag to emit run-time type information for anything in the block.. I know it wouldn't work for Typescript to control what is emitted, but adding a keyword might work?

export reflect class SomeClass {
} // Class //

export class SomeOtherClass {
  public reflect someMethod(): void {
  } // Procedure //
} // Class //
const reflect someVar: string = "hi";
const metaData = Reflect.getMetadata("design:type", someVar);

Ideally there would be more information emitted as well.. Like array with type, Enum Type information (what is the Enum's name, and values), and interface implementation checking.

Dear TS team,

I love you. You do amazing work. You are gods among mortals. You have brought JavaScript from the darkness, and given it the warm light of strong typing. Look upon us, the cowering meek masses, and understand that we live in the muck and mire of a world where we are doomed to endlessly crawl on our bellies through the alleys of Github seeking the the one true npm. We will forever wonder in the dark, until we have a type reflection model.

Serialization and validation without a reflective type system just doesn't work in the real world without endless boilerplate, or shiver, bespoke code generation from a schema file. The best solutions I could find are io-ts, zod, myzod, etc. The most courageous of us even resort to babel, but only to face the consequences. We have to declare our types in a round about library-bespoke way that is foreign to the uninitiated, and the libraries aren't even able to support all the wonderful type magic you work so hard to provide.

This is a case, where we urge you to take a step back and holistically look at what TypeScript users have to do to in the majority of modern projects, and list them out in priority order. The decision of how to tackle serialization, and what actually has to be done to have TypeScript still work with it is at the top of the list. Even if you still don't want to support reflection, please write a detailed doc, and put it in the official TypeScript documentation that explains how TypeScript users should tackle this problem or what library you recommend, so we don't have endlessly reinvent the wheel.

Second, and maybe even more importantly is type based dispatch. Redux actions need a discriminator property such as type: 'incrementCounter' Without runtime type information for the string literal, we resort to having to have the interface as a generic argument, and the string literal for the discriminator explicitly declared over and over. The moment we rename the type, the string literal discriminator needs to be renamed too, and the tools can't deal with it for us. The official Redux Toolkit's createAction does the best it can, but then their concept of "matchers" are now everywhere in your code base. This is because it's impossible to make a map of discriminator value to a TypeScript type to perform the downcast that you need for any message based dispatch map. Please add a way to automatically declare a property on an interface with the discriminator set to a string literal, and a casting map to disambiguate.

Side note, please don't solve this with decorators. A lot of us want to use interfaces. Decorators, like C# attributes are so coupling, and we can't add them to types from other libraries. A higher order function that's known to the compiler like: typescript.generateRuntimeType<T>() with options for discriminators etc. means it would even work with external libs.

Last thing. If you do this, I will send you cake. If it's in TypeScript 4.3, there will be ice-cream.

Love,
A humble disciple

Edit: Pinging Zeus himself: @ahejlsberg

Thanks for those links @akutruff. Since I discovered io-ts a few weeks ago, I discovered so many places where I can and should use it to remove custom boilerplate while increasing safety. It's very unfortunate to have to learn at least TWO wildly different syntaxes for defining types, in order to be a TypeScript programmer.

@akutruff I really did enjoy reading your comment :) It's probably been suggested many times before, but I do wonder if its just possible to have TypeScript emit a raw JSON schema literal object from a special kind of interface or type definition. Something like..

schema Foo {
   x: string,
   y: number,
   z: boolean
}
function test(foo: Foo) {
    JSON.validate(Foo, foo) 
    
    console.log(Foo.properties.x.type) // string
    console.log(Foo.properties.y.type) // number
    console.log(Foo.properties.z.type) // boolean
}

Where in the above example, Foo is seen as a TS type when used as a annotation, but seen as a JSON schema object when used in code. One thing I noticed about TypeScript is that it's already possible to share a name (Foo in this case) as a type or variable which TS is able to infer appropriately through usage. For reference, I maintain this project where I use this feature quite often.

type Foo = Static<typeof Foo> // <--+ 
                              //    |--- Note: TypeScript allows the Foo to be both variable and type.
const Foo = Type.Object({     // <--+
   x: Type.String(),
   y: Type.Number(),
   z: Type.Boolean()
})

function test(foo: Foo) {    // Seen as a type { x: string, y: number, z: boolean } when annotated.
    JSON.validate(Foo, foo)      // Seen as a json schema object when used explicitly.
    
    console.log(Foo.properties.x.type) // string
    console.log(Foo.properties.y.type) // number
    console.log(Foo.properties.z.type) // boolean
}

I guess for the purposes of runtime data type validation, JSON schema might be a pretty good emit target for TypeScript as it wouldn't need to invent it's own validation logic (AJV is pretty good and tied to JSON schemas standard / drafts). However, as JSON schema doesn't provide anything to validate or derive method signatures, I'm not sure if it ticks everyones boxes (with nods to .NET System.Reflection and all the goodness that brings). But JSON schema is probably the closest thing TS could get to emitting a reflective object type without implementing a full blown reflection system (which is most likely far far out of scope).

In terms of what's emitted to JavaScript, perhaps something like the following.

schema Foo {
   x: string,
   y: number,
   z?: boolean
}

// would emit as

const Foo = {
  type: 'object',
  properties: {
    x: { type: 'string' },
    y: { type: 'number' },
    z: { type: 'boolean' }
  },
  required: ['x', 'y']
}

Also, side note. I'm pleased this discussion thread is still active after all these years :D

Quick Update: For reference, the following is how TS types could potentially map over to JSON schema.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TypeScript                  β”‚ Emit to JavaScript          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = any                β”‚ const T = { }               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = unknown            β”‚ const T = { }               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = string             β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'string'           β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = number             β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'number'           β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = boolean            β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'boolean'          β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = null               β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'null'             β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = string             β”‚ const T = {                 β”‚
β”‚ (as regex)                  β”‚    type: 'string',          β”‚
β”‚                             β”‚    pattern: 'foo'           β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = 'foo'              β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'string',          β”‚
β”‚                             β”‚    const: 'foo'             β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = number[]           β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'array',           β”‚
β”‚                             β”‚    items: {                 β”‚
β”‚                             β”‚      type: 'number'         β”‚
β”‚                             β”‚    }                        β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = {                  β”‚ const T = {                 β”‚
β”‚      [key: string]          β”‚    type: 'object'           β”‚
β”‚ } : number                  β”‚    additionalProperties: {  β”‚
β”‚                             β”‚      type: 'number'         β”‚
β”‚                             β”‚    }                        β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = {                  β”‚ const T = {                 β”‚
β”‚    name: string,            β”‚   type: 'object',           β”‚
β”‚    email: string            β”‚   properties: {             β”‚
β”‚ }                           β”‚      name: {                β”‚
β”‚                             β”‚        type: 'string'       β”‚
β”‚                             β”‚      },                     β”‚
β”‚                             β”‚      email: {               β”‚
β”‚                             β”‚        type: 'string'       β”‚
β”‚                             β”‚      }                      β”‚
β”‚                             β”‚   },                        β”‚
β”‚                             β”‚   required: [               β”‚
β”‚                             β”‚      'name',                β”‚
β”‚                             β”‚      'email'                β”‚
β”‚                             β”‚   ]                         |
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = [string, number]   β”‚ const T = {                 β”‚
β”‚                             β”‚    type: 'array',           β”‚
β”‚                             β”‚    items: [                 β”‚
β”‚                             β”‚       {                     β”‚
β”‚                             β”‚         type: 'string'      β”‚
β”‚                             β”‚       }, {                  β”‚
β”‚                             β”‚         type: 'number'      β”‚
β”‚                             β”‚       }                     β”‚
β”‚                             β”‚    ],                       β”‚
β”‚                             β”‚    additionalItems: false,  β”‚
β”‚                             β”‚    minItems: 2,             β”‚
β”‚                             β”‚    maxItems: 2,             β”‚
β”‚                             β”‚ }                           |
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ enum Foo {                  β”‚ const T = {                 β”‚
β”‚   A,                        β”‚    enum: [0, 1]             β”‚
β”‚   B                         β”‚ }                           β”‚
β”‚ }                           β”‚                             β”‚
β”‚                             β”‚                             β”‚
β”‚ type T = Foo                β”‚                             β”‚
β”‚                             β”‚                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = string | number    β”‚ const T = {                 β”‚
β”‚                             β”‚    anyOf: [{                β”‚
β”‚                             β”‚       type: 'string'        β”‚
β”‚                             β”‚    }, {                     β”‚
β”‚                             β”‚       type: 'number'        β”‚
β”‚                             β”‚    }]                       β”‚
β”‚                             β”‚ }                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ type T = {                  β”‚ const T = {                 β”‚
β”‚    a: string                β”‚   allOf: [{                 β”‚
β”‚ } & {                       β”‚     type: 'object',         β”‚
β”‚    b: number                β”‚     properties: {           β”‚
β”‚ }                           β”‚        a: {                 β”‚
β”‚                             β”‚          type: 'string'     β”‚
β”‚                             β”‚        }                    β”‚
β”‚                             β”‚     },                      β”‚
β”‚                             β”‚     required: ['a']         β”‚
β”‚                             β”‚   }, {                      β”‚
β”‚                             β”‚     type: 'object',         β”‚
β”‚                             β”‚     properties: {           β”‚
β”‚                             β”‚       b: {                  β”‚
β”‚                             β”‚         type: 'number'      β”‚
β”‚                             β”‚       }                     β”‚
β”‚                             β”‚     },                      β”‚
β”‚                             β”‚     required:['b']          β”‚
β”‚                             β”‚   }]                        β”‚
β”‚                             β”‚ }                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

After four years of using TS professionally and loving it due to the productivity it offers, this is one the one feature I hope gets implemented some day. Having type information at runtime would be a god-send.

https://www.typescriptneedstypes.com

Github

After reading this thread, I gathered all the projects listed, and the ones I've found from my searches. The goal is to centrally track how this is affecting us as a community to give empirical data on the impact to the TypeScript team.

I included all the project links in this thread. Please file an issue or send a PR to add to the list of attempted solutions. If your project is affected by this problem, or simply want to include a link to your GH account, I'll add those too. Please ask colleagues about their experience as well. All contributions welcome.

If the TypeScript team is just gonna let this die can they at least give us the necessary plugin APIs so we can do it ourselves?

I understand the resistance to fragmentation but it's happening anyways. Put it behind a flag, document that it may break interoperability (as other options do), and leave it at that.

I also want to note that I think TypeScript's ideology of not implementing certain features to prevent fragmentation creates more fragmentation. If TypeScript doesn't adopt a high-demand feature, then there's going to be dozens of libraries created (as we are seeing here) that have different strategies, APIs, and functionality, trying to fill the gap.

I've created a proposal and implementation for full runtime types with a reflection API: #47658. It's not trivial, but I think has some nice and advanced features and characteristics (like small footprint of emitted type information) worth considering. Supporting all type expressions of TypeScript in runtime is a my personal desire, so its supported. Would appreciate your thoughts on this. If you like it, please leave a πŸ‘ at the issue so the TypeScript team sees the demand.

I have been working on an external solution as well, though I've been quite busy with other things it is still very much something I want to bring to release quality, and would love to collaborate- see https://github.com/rezonant/typescript-rtti

As for doing this directly within Typescript, the issue is complicated in that decorators are not in stage 3, and the inclusion of decorators and runtime type metadata runs afoul of the project goals in that Typescript wishes not to add runtime functionality beyond downlevelling standardized Javascript features.

Ultimately the best way to solve this is to build external solutions and work to get a community consensus on adoption and mechanism. Those external solutions must be engineered with an eye toward inoperability because it will be a long time before one solution rises to the position of a defacto standard.

I've made my own reflection system too => tst-reflect.
And then I've built the Dependency Injection of top of that, see.

tst-reflect is based on custom transformer and it generates metadata about types, accessible by getType<SomeType>(): Type function. Instance of Type class is returned which contains quite a lot of methods to work with types. All the types inside/out are instances of Type.

Generic types supported!
It works with quite complex types, unions, intersections, enums, details about interfaces and classes (constructors, properties, methods, parameters, generic types, decorators,.. ) and much more.

Usage is inspired by C# reflection.

Here is REPL with example. Runtime Type, even runtime generic

Simplified version of that REPL.

function printClassInfo<TType>()
{
	const type = getType<TType>(); // <<== Here is the generic type used!

	if (!type.isClass())
	{
		return;
	}

	console.log("class " + type.name);
	console.log("full type identifier: " + type.fullName);

	const properties = type.getProperties();
	const methods = type.getMethods();

	console.log("Properties");
	console.log(
		properties.map(prop => 
			`${AccessModifier[prop.accessModifier]} ${Accessor[prop.accessor]} ${prop.name}: ${prop.type.name}
		).join("\n")
	);

	console.log("Methods");
	console.log(
		methods.map(method => AccessModifier[method.accessModifier] + " " + method.name
			+ "("
			+ method.getParameters().map(param => param.name + ":" + param.type.name).join(", ")
			+ "): " + method.returnType.name
			+ (method.optional ? " [optional]" : "")
		).join("\n")
	);
}

class Bar {
    foo: string;
    bar: any;
    toSomething(): void;
}

printClassInfo<Bar>();

Perhaps those in the community who have built or are working on reflection systems could collaborate together on a shared protocol. If TS doesn't want to bake this in but there's a community protocol that is implemented by the various reflection provider libraries/tools, then other libraries and frameworks could be written in such a way as to work with any compatible implementation.

This is one of the things we're currently doing in the web components world. There are a number of libraries for building web components and a number of W3C standards related to that. But there are features not covered by the standards (and probably never will be) that we want our various libraries to be able to interop around. So we meet to design community protocols and gain consensus around those.

Yes, there are so many implementations that differ and are incompatible, and yet none that are dominant for one reason or another. Perhaps we can set up a working group to come to some base consensus on what a shared metadata format should look like and what it's goals and nongoals should be.

Question would be where to start the process -- @akutruff Your existing repository could be a good place to organize these efforts, or we could create a new github org to host such a project. The primary deliverable would be a specification for the metadata format, preferable one that is extensible and I think we should reach out to all of the folks who have built their own solutions and invite them to participate.

EDIT: We could also build a reference implementation of course

@EisenbergEffect I appreciate the sentiment. I really do. The issue is that we don't have what we need to build what we need. We are walled off. No amount of collaboration will change this. There are really good efforts, but we all hit the same wall for the same reasons. The type information is in the compiler, ready to be emitted. That's the source of truth, and any effort to build without it will result in the same dead-ends we get today. tsc --emit-types --emit-ast into blobs of json is the basic needand we can take it from there, but it ain't happening. We don't have that in a first class fashion, and we will always have to jump through the hoops that all the projects listed in the typescript-needs-types repository have to do. We're at a dead-end because no-one, rationally, wants to couple their projects to babel plugins, or the TypeScript-like fake DSL schema madness.

One of the great things about TypeScript, is that they built the language with IDE's as a first class citizen. In the past, IDE's needed to maintain their own parsers, AST's etc, and it was always double the work, caused fragility, and only a few select companies with the resources to do maintain these duplicate efforts could afford it. (e.g. DevExpress) The lack of type emission is precisely the same situation. We are walled off and forced to replicate what the compiler is already doing. It's not optional. All implementations must either contaminate the code, or put it their own meta-compiler tech.

I'm going to say the thing I shouldn't say which is why I don't make the big bucks: The effort is blocked by dogma without consideration of on-the-ground reality. The TypeScript team can emit types without violating the spirit of their rules, which I agree with - emit human readable JavaScript, and that's that. It makes 100% sense. However, the compiler emits type files right next to those JS files so the type information is preserved for editing experiences... They can emit machine readable type schemas too, and let us use them at runtime. It's so utterly arbitrary that I want to sit in a corner and stare out a window listening to Γ‰dith Piaf.

I disagree that we have no path forward, transformers are totally capable of achieving the desired result and though their DX leaves something to be desired, they definitely provide the avenue that solves the problem, but the next problem is getting everyone on the same page and using compatible, or ideally the same, implementation

(see #14419 for a better transformer DX)

However, the compiler emits type files right next to those JS files so the type information is preserved for editing experiences... They can emit machine readable type schemas too, and let us use them at runtime

I think that emitting next to the code instead of within the code limits the potential avenues they can be used (or at least heavily complicates it). Should I use fetch() to get the type information or fs.readFile? How do I know I'm on Node.js or Deno? Should this code need to know that just to get runtime type info?