

Getting started
You can install dtsr with:
npm install --global dtsr
Or run directly with npx:
npx dtsr --help
Hello World
Create 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)
Eval a type expression in a file context
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.
Documentation
Overview
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
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"'
How it works
-
With a source file: By default, dtsr looks for a type named Main in your .d.ts file.
- If you pass extra arguments (
[args...]) after the file name, these will be provided as Main<[...args]>.
- If no additional arguments are given, it simply evaluates
Main.
-
With --eval: Instead of looking for Main, dtsr will 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.
-
Reading configuration: By default, dtsr tries to find and use a local tsconfig.json to respect any custom compiler settings you might have.
- If you want to point to a specific tsconfig file, use
-p or --project.
-
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.
CLI options in detail
-
<source-file>
The .d.ts file 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 your Main type as Main<[...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 of dtsr.
-
-h, --help
Displays the usage help message.
Basic example
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?"
Advanced usage
- 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 dtsr to the top of your file, and make them executable with chmod +x ./main. Now you can run them directly from the command line:
#!/usr/bin/env dtsr
export type Main = "Hello World!"
Now you can run ./main directly from the terminal.
[!NOTE]
For the shebang to work, you need to have dtsr installed globally or in your PATH.
Common Gotchas
-
No Main type found: If you don't provide --eval and dtsr can't find Main in the file, you'll get an error. Either rename your main entry type to Main or use --eval.
-
Invalid arguments: All [args...] passed after the source file are interpreted as strings. Make sure your Main type 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.
FizzBuzz.d.ts
> 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
Fibonacci.d.ts
> dtsr ./Fibonacci.d.ts 10
55
type 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>>
ParseQueryString.d.ts
> 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]: [''] }
Is this project for real?
No (But it does work as described)