Declaration TypeScript Runner (dtsr): The easiest way to run your .d.ts files
You can install dtsr with:
npm install --global dtsrOr run directly with npx:
npx dtsr --helpCreate a file Hello.d.ts:
export type Main<Argv extends string[]>
= `Hello ${Argv[0]}!`Run with:
> dtsr ./Hello.d.ts world
"Hello world!"Tip
Making .d.ts executables:
You can make your .d.ts files executable by adding the shebang line:
#!/usr/bin/env dtsr
(don't forget to chmod +x ./Hello.d.ts)
By default, dtsr pass the command-line arguments to the type Main on the passed file, but you can also evaluate a custom expression using the --eval flag:
> dtsr --eval 'Main<["world"]>' ./Hello.d.ts
"Hello world!"In fact, if you just want to quickly evaluate a type expression, you don't even need to create a file:
> dtsr --eval '"world" extends string ? "yes" : "no"'
"yes"Tip
Checkout our Community Examples to see more complex use cases.
dtsr (Declaration TypeScript Runner) lets you evaluate types from a TypeScript declaration file (.d.ts), a normal TypeScript (.ts, .tsx) or directly from an inline expression.
You can think of it as a quick way to “run” your type-level logic instead of hoping that hovering a symbol in VSCode will not truncate the type beyond recognition.
Usage: dtsr [options] <source-file> [args...]
Evaluate type-level expressions from TypeScript declaration files.
Options:
-v, --version Output the version number
-e, --eval <expression> Evaluate a type expression in the context of the source file
-p, --project <path> Specify the TypeScript configuration file (default: tsconfig.json)
-h, --help Display this help message
Examples:
$ dtsr ./Main.d.ts "Joe"
$ dtsr --eval 'Foo<string, number>' ./Main.d.ts
$ dtsr --project tsconfig.prod.json ./Main.d.ts "Joe"
$ dtsr --eval '"hello" extends string ? "yes" : "no"'-
With a source file: By default, dtsr looks for a type named
Mainin your.d.tsfile.- If you pass extra arguments (
[args...]) after the file name, these will be provided asMain<[...args]>. - If no additional arguments are given, it simply evaluates
Main.
- If you pass extra arguments (
-
With --eval: Instead of looking for
Main,dtsrwill create a temporary type alias to evaluate whatever expression you provide.- If you also specify a
<source-file>, that file's types will still be in scope, so you can reference them from your expression.
- If you also specify a
-
Reading configuration: By default,
dtsrtries to find and use a localtsconfig.jsonto respect any custom compiler settings you might have.- If you want to point to a specific tsconfig file, use
-por--project.
- If you want to point to a specific tsconfig file, use
-
Behind the scenes:
- dtsr creates a in-memory TypeScript file that inlines your expression as type __dtsr_result_type = ....
- It then uses the TypeScript compiler APIs to compute the type's string representation.
-
<source-file>
The.d.tsfile you want to evaluate. Must be provided if you do not use--eval. -
[args...]
Additional string arguments appended after the<source-file>. These get fed into yourMaintype asMain<[...args]>. -
-e, --eval <expression>
Evaluates a custom type expression:dtsr --eval "Foo<string, number>"If a
<source-file>is also specified, you can reference its exported types in your expression. -
-p, --project <path>
Specifies a custom tsconfig.json to use instead of automatically searching for one in the current directory:dtsr --project tsconfig.prod.json ./MyTypes.d.ts
-
-v, --version
Displays the current version ofdtsr. -
-h, --help
Displays the usage help message.
Hello.d.ts:
export type Main<Argv extends string[]>
= `Hello ${Argv[0]}!`Run:
> dtsr ./Hello.d.ts world
"Hello world!"The use of command line arguments is optional, instead you can:
export type Main = "Hello world!"Run:
> dtsr ./Hello.d.ts
"Hello world!"Or you can just evaluate an expression using the types defined on the file:
> dtsr --eval '`${Main} How are you?`' ./Hello.d.ts
"Hello world! How are you?"- Evaluate any expression without a file
You can skip specifying a source file entirely by just using --eval. For instance:
> dtsr --eval '"world" extends string ? "yes" : "no"'
"yes"- Use with a custom tsconfig
If you have a custom tsconfig file, you can specify it with --project:
> dtsr --project tsconfig.prod.json ./MyTypes.d.ts-
Make files directly executable
Add the shebang line#!/usr/bin/env dtsrto the top of your file, and make them executable withchmod +x ./main. Now you can run them directly from the command line:#!/usr/bin/env dtsr // file: ./main export type Main = "Hello World!"
Now you can run
./maindirectly from the terminal.[!NOTE] For the shebang to work, you need to have
dtsrinstalled globally or in your PATH.
-
No
Maintype found: If you don't provide--evalanddtsrcan't findMainin the file, you'll get an error. Either rename your main entry type toMainor use--eval. -
Invalid arguments: All
[args...]passed after the source file are interpreted as strings. Make sure yourMaintype is expecting that structure (string[]).
For more examples, head over to the Community Examples section to see how you can leverage type-level programming to solve interesting problems.
> dtsr ./FizzBuzz.d.ts 15
["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"]type Main<Argv extends string[]>
= Take<ParseInt<Argv[0]>, FizzBuzzIterator[1]>
type FizzBuzzIterator<
S extends Record<any, any> = { 3: []; 5: []; i: [] },
R extends string = `${S[3]['length'] extends 3 ? 'Fizz': ''}${S[5]['length'] extends 5 ? 'Buzz' : ''}`,
> = [
R extends '' ? `${S['i']['length']}` : R,
FizzBuzzIterator<{
3: S[3]['length'] extends 3 ? [1] : [...S[3], 1]
5: S[5]['length'] extends 5 ? [1] : [...S[5], 1]
i: [...S['i'], 1]
}>,
]
type Take<N extends number, Cons extends [any, any], Acc extends any[] = []> = Acc['length'] extends N ? Acc : Take<N, Cons[1], [...Acc, Cons[0]]>
type ParseInt<S extends string> = S extends `${infer N extends number}` ? N : never> dtsr ./Fibonacci.d.ts 10
55type Main<Argv extends string[]>
= Fib<ParseInt<Argv[0]>>
type Fib<X extends number>
= X extends 0 ? 0
: X extends 1 ? 1
: Add<Fib<Sub<X, 1>>, Fib<Sub<X, 2>>>
type ParseInt<S extends string> = S extends `${infer N extends number}` ? N : never
type Repeat<N extends number, Acc extends true[] = []> = Acc['length'] extends N ? Acc : Repeat<N, [...Acc, true]>
type Tail<T extends any[]> = T extends [any, ...infer U] ? U : never
type Add<A extends number, B extends number> = [...Repeat<A>, ...Repeat<B>]['length']
type Dec<A extends number> = Tail<Repeat<A>>['length']
type Sub<A extends number, B extends number> = B extends 0 ? A : Sub<Dec<A>, Dec<B>>> dtsr ./ParseQueryString.d.ts 'xs=1&xs=2&y=3&flag'
{ xs: ["1", "2"]; flag: [""]; y: ["3"]; }type Main<Argv extends string[]>
= Parse<Argv[0]>
type Parse<Q extends string>
= Q extends '' ? {}
: Q extends `${infer Head}&${infer Tail}` ?
([Parse<Head>, Parse<Tail>] extends [infer A, infer B] ?
{ [K in keyof A | keyof B]: [...(K extends keyof A ? Extract<A[K], any[]> : []), ...(K extends keyof B ? Extract<B[K], any[]> : []) ] } : never)
: Q extends `${infer K}=${infer V}` ? { [P in K]: [V] }
: { [P in Q]: [''] }No (But it does work as described)