Skip to content
/ dtsx Public

⚡ Extremely fast, smart & configurable DTS emitter.

License

Notifications You must be signed in to change notification settings

stacksjs/dtsx

Social Card of this repo

npm version GitHub Actions Commitizen friendly

dtsx

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.

Features

  • 🎯 Sound type inference with @defaultValue preservation — no isolatedDeclarations needed
  • ⚡ 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

Type Inference — dtsx vs tsc vs oxc

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.

Why @defaultValue?

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 Constants

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 = true
port debug
dtsx 3000 true
tsc 3000 true
oxc 3e3 (mangled!) boolean

Object Properties — @defaultValue Preservation

// 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
};

Generic Type Replacement

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

Deep as const

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 const

dtsx 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']
};

Promise & Complex Types

export const promiseVal = Promise.resolve(42)
Tool Output
dtsx Promise<42> (resolved values are immutable)
tsc Promise<number>
oxc unknown (error — requires explicit annotation)

Full Comparison

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.

Install

bun install -d @stacksjs/dtsx

@npmjs.com, please allow us to use the dtsx package name 🙏

Get Started

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
  }
}

Library

Given the npm package is installed, you can use the generate function to generate TypeScript declaration files from your project.

Usage

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 generate

CLI

The dtsx CLI provides a simple way to generate TypeScript declaration files from your project. Here's how to use it:

Generate Command

Generate declaration files using the default options:

dtsx generate

Or 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 --version

Watch Command

Watch for changes and regenerate automatically:

# Watch with default options:
dtsx watch

# Watch specific directory:
dtsx watch --root src --outdir dist/types

Stdin Command

Process 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.ts

Available Options

Basic 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.

Benchmarks

Benchmarked on Apple M3 Pro, macOS (bun 1.3.10, arm64-darwin). Run bun benchmark/index.ts to reproduce.

In-Process API — Cached

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)

In-Process API — No Cache

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)

CLI — Single File

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)

Multi-File Project

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)

Binary Size

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

Testing

bun test

Changelog

Please see our releases page for more information on what has changed recently.

Contributing

Please review the Contributing Guide for details.

Community

For help, discussion about best practices, or any other conversation that would benefit from being searchable:

Discussions on GitHub

For casual chit-chat with others using this package:

Join the Stacks Discord Server

Postcardware

“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 🌎

Sponsors

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.

Credits

License

The MIT License (MIT). Please see LICENSE for more information.

Made with 💙

About

⚡ Extremely fast, smart & configurable DTS emitter.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors 7