Home > Back-end >  How do typescript libraries manage to read types at runtime
How do typescript libraries manage to read types at runtime

Time:04-16

When source code is compiled from typescript to javascript, type annotations are stripped away and there is no way to check the type of a variable at runtime.

However, there are many typescript libraries that seem to change behaviour based on type annotations of class properties. For example, when writing typeorm entities, we could write something like:

@Entity()
class MyEntity extends BaseEntity {
  @Field()
  public id: number // automatically infers int database type

  @Field()
  public description: string // automatically infers varchar or text database type

  @Field()
  public image: string | null // cannot infer correct type, will throw error

}

We also have something similar with typedi (passes correct reference through constructor), type-graphql (builds graphql schema with correct graphql type) etc. I get it when you have to pass a function through a decorator or something similar, but how do these libraries infer types from only type annotations?

CodePudding user response:

I got curious, and found the answer in the TypeORM documentation here:

TypeScript configuration

Also, make sure you are using TypeScript version 4.5 or higher, and you have enabled the following settings in tsconfig.json:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

I suspected that told TypeScript to provide runtime type information to decorators, and indeed, it does:

Emit Decorator Metadata

emitDecoratorMetadata

Enables experimental support for emitting type metadata for decorators which works with the module reflect-metadata.

When you have that enabled, TypeScript emits calls to the decorators that include type information at runtime. The example in the documentation is for this code:

class Demo {
  @LogMethod
  public foo(bar: number) {
    // do nothing
  }
}

...which emits __decorate and __metadata functions, then this:

class Demo {
    foo(bar) {
        // do nothing
    }
}
__decorate([
    LogMethod,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", void 0)
], Demo.prototype, "foo", null);

If we look at your example, this is what it emits for decorator calls:

__decorate([
    Field(),
    __metadata("design:type", Number)
], MyEntity.prototype, "id", void 0);
__decorate([
    Field(),
    __metadata("design:type", String)
], MyEntity.prototype, "description", void 0);
__decorate([
    Field(),
    __metadata("design:type", Object)
], MyEntity.prototype, "image", void 0);
MyEntity = __decorate([
    Entity()
], MyEntity);

Playground link

  • Related