Skip to content

[Proposal] Implement a way to emit json type information #12605

Closed
@tarruda

Description

@tarruda

Imagine a node.js server that has the following class:

class Calculator {
   add(a: number, b: number): number { return a + b; }
}

This class can be consumed in a type safe manner if the client is also a typescript application, but what if the client consumes this class through a RPC system? While type declarations could be generated for typescript clients, they would not be useful if the RPC client is written in pure javascript or any other language. One way to address the above problem is by manually validating each parameter before passing to the class instance, but this would hurt the DRY principle.

To address this and other runtime type-validating problems, I propose a new keyword that would make type information available at runtime. Something like this:

class Calculator {
    add(a: number, b: number): number { return a + b; }
}

const TypeRegistry = reflect Calculator;

reflect(or whatever keyword is chosen) is a static expression that returns a JSON-compatible object containing rich type information about all arguments passed to it.

In the above example, the compiler would generate the following code:

class Calculator {
    add(a, b) { return a + b; }
}
const TypeRegistry = [{
  type: 'class',
  name: 'Calculator',
  methods: [
    {name: 'add', returns: 'number', params: [{type: 'number', name: 'a'}, {type: 'number', name: 'b'}]}
  ]
}];

This metadata could be used not only to generate automatic server-side validation for RPC-exposed APIs, but would also create more possibilities for typescript libraries/application. For example, imagine a typescript database library that automatically checks type compatibility in the database, forcing the user to write a migration script if the schema stored in the DB doesn't match the current type information(I would certainly prefer to write strongly typed database libraries in typescript then!)

Note that an array is created instead of a single JSON object. This is required because classes/interfaces are very likely to reference other user-defined types, and such types would also have to be emitted. Each type would be an object in the array, and would be referred by its index. For example:

interface User {
  name: string;
}

class Calculator {
    add(a: number, b: number): number { return a + b; }
    getLastUser(): User {
       return {name: 'John Doe'};
    }
}

const TypeRegistry = reflect Calculator;

would generate:

class Calculator {
    add(a, b) { return a + b; }
    getLastUser() {
        return { name: 'John Doe' };
    }
}

const TypeRegistry = [{
  type: 'class',
  name: 'Calculator',
  methods: [
    {name: 'add', returns: 'number', params: [{type: 'number', name: 'a'}, {type: 'number', name: 'b'}]},
    {name: 'getLastUser', returns: 1, params: []}
  ]
}, {
  type: 'interface',
  name: 'User',
  properties: [
    {name: 'name', type: 'string'}
  ]
}];

Note that if a number is passed when a type name is expected, it should be considered the index of an user-defined type in the same registry. To allow the programmer to create a unified registry for an entire API, it should be possible to pass multiple types to reflect:

const TypeRegistry = reflect Calculator, User

To handle generics in a simplified manner, only concrete types should be accepted as arguments to reflect. For example:

interface Database<TKey, TValue> {
  get(k: TKey): Promise<TValue>;
  put(k: TKey, v: TValue): Promise<void>;
}
const TypeRegistry = reflect Database<number, string>;  // ok
const TypeRegistry = reflect Database; // compiler error

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions