Awesome
tsruntime
Typescript custom transformer for emitting types for using in runtime (reflection).
Motivation
Sometimes you need types metadata in the runtime, e.g. to validate backend response or set propTypes
from interface Props{}
Prerequisites
typescript: =>5.0.0
Setup
- install
tsruntime
andts-patch
- change
tsconfig.json
:
{
"compilerOptions": {
"experimentalDecorators": true, // if you'll use decorators
"plugins": [
{ "transform": "tsruntime/dist/transform/transformer.js", "type": "program" },
],
}
}
- run with
ts-patch
compiler:ts-node --compiler=ts-patch/compiler src/index.ts
tspc
- change
compiler
inawesome-typescript-loader
config - etc
See Example
Warning: You cannot use transpileOnly
compiling mode and isolatedModules
(if you want reflect types from imported modules).
Usage:
Using reflect
function
import {reflect} from 'tsruntime';
interface StatsModel {
a?: number
b: string
c: Array<string>
d: number | string | null
}
const type = reflect<StatsModel>()
console.log(type)
const type2 = reflect<string>()
On compiled code you'll have
var type = tsruntime_1.reflect({
kind: 15 /*Object*/,
name: "StatsModel",
properties: {
a: {
kind: 17 /*Union*/,
types: [{ kind: 12 /*Undefined*/ }, { kind: 3 /*Number*/ }]
},
b: { kind: 2 /*String*/ },
c: {
kind: 18 /*Reference*/,
type: Array,
arguments: [{ kind: 2 /*String*/ }]
},
d: {
kind: 17 /*Union*/,
types: [
{ kind: 13 /*Null*/ },
{ kind: 2 /*String*/ },
{ kind: 3 /*Number*/ }
]
}
}
})();
Using class decorators
import {Reflective, getClassType} from 'tsruntime';
@Reflective
export class StatsModel {
a?: number
b!: string
c!: Array<string>
d!: number | string | null
}
@Reflective
class Foo extends Array<string> {
}
console.log(getClassType(StatsModel))
console.log(getClassType(Foo))
On runtime you'll have
// ...
StatsModel = __decorate(
[
tsruntime_1.Reflective({
kind: 19 /*Class*/,
name: "StatsModel",
properties: {
a: {
kind: 17 /*Union*/,
types: [{ kind: 12 /*Undefined*/ }, { kind: 3 /*Number*/ }]
},
b: { kind: 2 /*String*/ },
c: {
kind: 18 /*Reference*/,
type: Array,
arguments: [{ kind: 2 /*String*/ }]
},
d: {
kind: 17 /*Union*/,
types: [
{ kind: 13 /*Null*/ },
{ kind: 2 /*String*/ },
{ kind: 3 /*Number*/ }
]
}
}
})
],
StatsModel
);
// ...
Using types info
import {Types, reflect} from 'tsruntime';
const isString = reflect<string>().kind === Types.TypeKind.String
Customization
You can customize both reflect
and Reflective
to do whatever you want
import {createReflective, Types} from 'tsruntime';
const storage = {} as any
function MyReflective (key: string) {
return createReflective(reflectedType => {
return (target: any) => {
storage[key] = reflectedType
}
})
}
// typeof MyReflective('realcls') is MarkReflective type.
@MyReflective('realcls')
class Cls {
prop = 42
}
// in compiled code - @MyReflective('realcls')({kind: ...})
const clsType = storage['realcls']
const validateResp = createReflective(
reflectedType => <T>(resp: unknown) => { //should have <T>
if (reflectedType.kind === Types.TypeKind.String) {
return typeof resp === 'string'
}
}
)
const isValid = validateResp<string>('asd')
// in compiled code - validateResp({kind: ...})('asd')
How it works
createReflective
expects reflectedType => T
to be passed as arg.
It returns T
and marks it as reflectable (type MarkReflective
).
Transformer see that symbol have type MarkReflective
and calls it with reflectedType
.
For function it gets type from first generic argument (thats why you need <T>
), for decorator - from class declaration, related to decorator.