A library that helps you generate TypeScript declaration files from your project. Given we do not know the user's input ever, we need to never hardcode based results based from our examples, always create a dynamic solution.
- 🎯 Sound type inference with
@defaultValuepreservation — noisolatedDeclarationsneeded - ⚡ Extremely fast .d.ts generation
- 🔄 Parallel processing support
- 📥 Stdin/stdout support for piping
- ⚙️ Highly configurable
- 🪶 Lightweight library
- 🤖 Cross-platform binary
- 👀 Watch mode for development
- ✅ Built-in validation
dtsx generates sound, narrow types with @defaultValue preservation — no isolatedDeclarations flag required, no explicit type annotations needed. Where tsc and oxc silently discard original values when widening types, dtsx preserves them as standard @defaultValue JSDoc so they surface in IDE hover tooltips.
All output below is real — same source file, three tools, nothing hand-edited.
In TypeScript, const only makes the binding immutable — object properties and array elements remain mutable. This means const config = { timeout: 5000 } allows config.timeout = 9999, so the declared type must be number, not 5000.
All three tools correctly widen mutable container properties. The difference is what happens to the original values:
| Tool | Widened type | Original value preserved? |
|---|---|---|
| dtsx | /** @defaultValue 5000 */ timeout: number |
Yes — via @defaultValue JSDoc |
| tsc | timeout: number |
No — value lost entirely |
| oxc | timeout: number |
No — value lost entirely |
Scalar const bindings are truly immutable — const port = 3000 can never change. All tools keep the literal type:
// Source
export const port = 3000
export const debug = trueport |
debug |
|
|---|---|---|
| dtsx | 3000 |
true |
| tsc | 3000 |
true |
| oxc | 3e3 (mangled!) |
boolean |
// Source
export const config = {
apiUrl: 'https://api.stacksjs.org',
timeout: 5000,
features: { darkMode: true, notifications: false },
routes: ['/', '/about', '/contact'],
}| Property | dtsx | tsc | oxc |
|---|---|---|---|
apiUrl |
string + @defaultValue 'https://...' |
string |
string |
timeout |
number + @defaultValue 5000 |
number |
number |
darkMode |
boolean + @defaultValue true |
boolean |
boolean |
routes |
string[] |
string[] |
unknown (error) |
Top-level @defaultValue |
full object literal | (none) | (none) |
dtsx output:
/**
* @defaultValue
* ```ts
* {
* apiUrl: 'https://api.stacksjs.org',
* timeout: 5000,
* features: { darkMode: true, notifications: false },
* routes: ['/', '/about', '/contact']
* }
* ```
*/
export declare const config: {
/** @defaultValue 'https://api.stacksjs.org' */
apiUrl: string;
/** @defaultValue 5000 */
timeout: number;
features: {
/** @defaultValue true */
darkMode: boolean;
/** @defaultValue false */
notifications: boolean
};
routes: string[]
};tsc and oxc output (values lost):
export declare const config: {
apiUrl: string;
timeout: number;
features: { darkMode: boolean; notifications: boolean };
routes: string[] // oxc errors here
};dtsx replaces broad generic annotations with narrow types inferred from the actual value:
// Source — generic index signature
export const conf: { [key: string]: string } = {
apiUrl: 'https://api.stacksjs.org',
timeout: '5000',
}| Tool | Output |
|---|---|
| dtsx | { apiUrl: 'https://api.stacksjs.org'; timeout: '5000' } |
| tsc | { [key: string]: string } — kept broad, lost all property info |
| oxc | { [key: string]: string } — kept broad, lost all property info |
When you explicitly use as const, all tools should preserve literal types. dtsx handles this correctly:
// Source
export const CONFIG = {
api: { baseUrl: 'https://api.example.com', timeout: 5000, retries: 3 },
features: { darkMode: true, notifications: false },
routes: ['/', '/about', '/contact'],
} as constdtsx output — every value preserved as a literal, arrays become readonly tuples, no @defaultValue needed (types are already self-documenting):
export declare const CONFIG: {
api: {
baseUrl: 'https://api.example.com';
timeout: 5000;
retries: 3
};
features: {
darkMode: true;
notifications: false
};
routes: readonly ['/', '/about', '/contact']
};export const promiseVal = Promise.resolve(42)| Tool | Output |
|---|---|
| dtsx | Promise<42> (resolved values are immutable) |
| tsc | Promise<number> |
| oxc | unknown (error — requires explicit annotation) |
| Declaration | dtsx | tsc | oxc |
|---|---|---|---|
const port = 3000 |
3000 |
3000 |
3e3 |
const debug = true |
true |
true |
boolean |
const items = [1,2,3] |
number[] + @defaultValue |
number[] |
unknown (error) |
config.apiUrl |
string + @defaultValue |
string |
string |
config.timeout |
number + @defaultValue |
number |
number |
config.routes |
string[] |
string[] |
unknown (error) |
conf (generic annotation) |
exact properties | { [key]: string } |
{ [key]: string } |
Promise.resolve(42) |
Promise<42> |
Promise<number> |
unknown (error) |
| Value info preserved? | Yes | No | No |
| Errors | 0 | 0 | 3 |
dtsx produces sound types (correctly widened for mutable containers) while preserving original values via @defaultValue JSDoc — something neither tsc nor oxc does. No as const, no explicit annotations, no isolatedDeclarations flag required.
bun install -d @stacksjs/dtsx@npmjs.com, please allow us to use the dtsx package name 🙏
There are two ways of using this ".d.ts generation" tool: as a library or as a CLI.
dtsx works out of the box — no isolatedDeclarations required. It infers narrow types directly from your source values. If you do enable isolatedDeclarations, dtsx uses it as a fast path to skip initializer parsing when explicit type annotations are present.
{
"compilerOptions": {
"isolatedDeclarations": true // optional — dtsx works great without it
}
}Given the npm package is installed, you can use the generate function to generate TypeScript declaration files from your project.
import type { DtsGenerationOptions } from '@stacksjs/dtsx'
import { generate, processSource } from '@stacksjs/dtsx'
const options: DtsGenerationOptions = {
cwd: './', // default: process.cwd()
root: './src', // default: './src'
entrypoints: ['**/*.ts'], // default: ['**/*.ts']
outdir: './dist', // default: './dist'
clean: true, // default: false
verbose: true, // default: false
keepComments: true, // default: true
// New options:
parallel: true, // default: false - process files in parallel
concurrency: 4, // default: 4 - number of concurrent workers
dryRun: false, // default: false - preview without writing
stats: true, // default: false - show generation statistics
validate: true, // default: false - validate generated .d.ts files
}
const stats = await generate(options)
console.log(`Generated ${stats.filesGenerated} files in ${stats.durationMs}ms`)
// You can also process source code directly:
const dtsContent = processSource(`
export const greeting: string = "Hello";
export function greet(name: string): string {
return greeting + " " + name;
}
`)
console.log(dtsContent)
// Output:
// export declare const greeting: string;
// export declare function greet(name: string): string;Available options:
Library usage can also be configured using a dts.config.ts (or dts.config.js) file which is automatically loaded when running the ./dtsx (or bunx dtsx) command. It is also loaded when the generate function is called, unless custom options are provided.
// dts.config.ts (or dts.config.js)
export default {
cwd: './',
root: './src',
entrypoints: ['**/*.ts'],
outdir: './dist',
keepComments: true,
clean: true,
verbose: true,
// Performance options
parallel: true,
concurrency: 4,
// Output options
stats: true,
validate: true,
// Filtering
exclude: ['**/*.test.ts', '**/__tests__/**'],
importOrder: ['node:', 'bun', '@myorg/'],
}You may also run:
./dtsx generate
# if the package is installed, you can also run:
# bunx dtsx generateThe dtsx CLI provides a simple way to generate TypeScript declaration files from your project. Here's how to use it:
Generate declaration files using the default options:
dtsx generateOr use custom options:
# Generate declarations for specific entry points:
dtsx generate --entrypoints src/index.ts,src/utils.ts --outdir dist/types
# Generate declarations with custom configuration:
dtsx generate --root ./lib --outdir ./types --clean
# Use parallel processing for large projects:
dtsx generate --parallel --concurrency 8
# Preview what would be generated (dry run):
dtsx generate --dry-run --stats
# Validate generated declarations:
dtsx generate --validate
# Exclude test files:
dtsx generate --exclude "**/*.test.ts,**/__tests__/**"
# Custom import ordering:
dtsx generate --import-order "node:,bun,@myorg/"
dtsx --help
dtsx --versionWatch for changes and regenerate automatically:
# Watch with default options:
dtsx watch
# Watch specific directory:
dtsx watch --root src --outdir dist/typesProcess TypeScript from stdin and output declarations to stdout:
# Pipe source code directly:
echo "export const foo: string = 'bar'" | dtsx stdin
# Process a file through stdin:
cat src/index.ts | dtsx stdin
# Chain with other tools:
cat src/utils.ts | dtsx stdin > dist/utils.d.tsBasic Options:
--cwd <path>: Set the current working directory (default: current directory)--root <path>: Specify the root directory of the project (default: './src')--entrypoints <files>: Define entry point files (comma-separated, default: '**/*.ts')--outdir <path>: Set the output directory for generated .d.ts files (default: './dist')--keep-comments: Keep comments in generated .d.ts files (default: true)--clean: Clean output directory before generation (default: false)--tsconfig <path>: Specify the path to tsconfig.json (default: 'tsconfig.json')
Performance Options:
--parallel: Process files in parallel (default: false)--concurrency <number>: Number of concurrent workers with --parallel (default: 4)
Output Options:
--verbose: Enable verbose output (default: false)--log-level <level>: Log level: debug, info, warn, error, silent (default: 'info')--stats: Show statistics after generation (default: false)--output-format <format>: Output format: text or json (default: 'text')--progress: Show progress during generation (default: false)--diff: Show diff of changes compared to existing files (default: false)
Validation Options:
--validate: Validate generated .d.ts files against TypeScript (default: false)--continue-on-error: Continue processing if a file fails (default: false)--dry-run: Preview without writing files (default: false)
Filtering Options:
--exclude <patterns>: Glob patterns to exclude (comma-separated)--import-order <patterns>: Import order priority patterns (comma-separated)
To learn more, head over to the documentation.
Benchmarked on Apple M3 Pro, macOS (bun 1.3.10, arm64-darwin). Run bun benchmark/index.ts to reproduce.
dtsx uses smart caching (hash check + cache hit) for watch mode, incremental builds, and CI pipelines.
| Tool | Small (~50 lines) | Medium (~100 lines) | Large (~330 lines) | XLarge (~1050 lines) |
|---|---|---|---|---|
| dtsx (cached) | 0.95 µs | 2.16 µs | 19.84 µs | 105.83 µs |
| zig-dtsx | 4.60 µs (4.8x) | 11.27 µs (5.2x) | 26.75 µs (1.3x) | 230.91 µs (2.2x) |
| oxc-transform | 6.76 µs (7.1x) | 20.54 µs (9.5x) | 79.54 µs (4.0x) | 519.44 µs (4.9x) |
| tsc | 194.34 µs (205x) | 438.12 µs (203x) | 1.14 ms (57x) | 4.20 ms (40x) |
Cache cleared every iteration for raw single-transform comparison.
| Tool | Small (~50 lines) | Medium (~100 lines) | Large (~330 lines) | XLarge (~1050 lines) |
|---|---|---|---|---|
| zig-dtsx | 4.68 µs | 11.43 µs | 27.89 µs | 230.32 µs |
| oxc-transform | 6.95 µs (1.5x) | 21.05 µs (1.8x) | 81.46 µs (2.9x) | 519.01 µs (2.3x) |
| dtsx (no-cache) | 10.42 µs (2.2x) | 23.06 µs (2.0x) | 67.79 µs (2.4x) | 400.81 µs (1.7x) |
| tsc | 155.16 µs (33x) | 389.90 µs (34x) | 918.21 µs (33x) | 3.82 ms (17x) |
All tools run as compiled native binaries via subprocess.
| Tool | Small (~50 lines) | Medium (~100 lines) | Large (~330 lines) | XLarge (~1050 lines) |
|---|---|---|---|---|
| zig-dtsx | 2.32 ms | 2.31 ms | 2.42 ms | 2.46 ms |
| oxc | 16.51 ms (7.1x) | 15.71 ms (6.8x) | 16.41 ms (6.8x) | 16.14 ms (6.6x) |
| dtsx | 29.42 ms (12.7x) | 29.36 ms (12.7x) | 30.96 ms (12.8x) | 32.30 ms (13.1x) |
| tsgo | 38.70 ms (16.7x) | 41.97 ms (18.2x) | 42.09 ms (17.4x) | 52.83 ms (21.5x) |
| tsc | 347.31 ms (150x) | 374.30 ms (162x) | 376.76 ms (156x) | 403.00 ms (164x) |
| Tool | 50 files | 100 files | 500 files |
|---|---|---|---|
| zig-dtsx | 12.16 ms | 23.23 ms | 109.33 ms |
| oxc | 35.38 ms (2.9x) | 58.62 ms (2.5x) | 402.32 ms (3.7x) |
| dtsx | 55.21 ms (4.5x) | 79.14 ms (3.4x) | 281.40 ms (2.6x) |
| tsgo | 210.54 ms (17.3x) | 413.69 ms (17.8x) | 2.18 s (20.0x) |
| tsc | 774.44 ms (63.7x) | 1.18 s (50.6x) | 3.99 s (36.5x) |
| Platform | Zig Binary | Bun Binary | Reduction |
|---|---|---|---|
| macOS arm64 | 659 KB | 61 MB | 95x smaller |
| macOS x64 | 716 KB | 67 MB | 96x smaller |
| Linux x64 | 6.2 MB | 108 MB | 17x smaller |
| Linux arm64 | 6.3 MB | 103 MB | 16x smaller |
| Windows x64 | 1.0 MB | 101 MB | 101x smaller |
| FreeBSD x64 | 5.5 MB | — | — |
bun testPlease see our releases page for more information on what has changed recently.
Please review the Contributing Guide for details.
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
“Software that is free, but hopes for a postcard.” We love receiving postcards from around the world showing where dtsx is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
The MIT License (MIT). Please see LICENSE for more information.
Made with 💙
