Bunli
Core Concepts

Type Inference

How Bunli provides automatic type inference for your CLI

Type Inference

Bunli provides automatic type inference through a unified type system that combines runtime validation with compile-time type safety. By using as const on command names and leveraging module augmentation, you get full type safety with zero manual type annotations.

How It Works

Bunli uses a revolutionary approach that combines:

  1. Module Augmentation - Generated types automatically extend the core type system
  2. Command Name Literals - Using as const enables automatic type inference
  3. Standard Schema Integration - Built-in type extraction from validation schemas
  4. Zero Manual Annotations - Handlers are automatically typed based on command names

The Magic: Automatic Type Inference

When you define a command with as const, the handler automatically gets the correct types:

export default defineCommand({
  name: 'deploy' as const, // ← REQUIRED: 'as const' enables type inference
  description: 'Deploy the application',
  options: {
    env: option(z.enum(['dev', 'staging', 'prod']), {
      description: 'Environment to deploy to'
    }),
    dryRun: option(z.boolean().default(false), {
      description: 'Run without making changes'
    })
  },
  
  // ✨ NO TYPE ANNOTATION NEEDED! ✨
  // flags is automatically typed as { env: 'dev' | 'staging' | 'prod', dryRun: boolean }
  handler: async ({ flags }) => {
    console.log(`Deploying to ${flags.env}`) // ← Full autocomplete!
    if (flags.dryRun) {
      console.log('Dry run mode')
    }
  }
})

Requirements

To use Bunli's automatic type inference, you need:

  1. as const Required - Command names MUST use as const for type inference
  2. Generated File Required - .bunli/commands.gen.ts must be in tsconfig.json
  3. Type Generation Required - Codegen must be enabled
  4. No Manual Annotations - Handler type annotations are automatic

Command Definition Pattern

Correct Pattern

export default defineCommand({
  name: 'deploy' as const, // ✅ 'as const' required
  options: { ... },
  handler: async ({ flags }) => { // ✅ No annotation needed!
    // flags is automatically typed!
  }
})

Incorrect Pattern

export default defineCommand({
  name: 'deploy', // ❌ No 'as const' - no type inference
  options: { ... },
  handler: async ({ flags }: { flags: { env: string } }) => { // ❌ Manual annotation not needed
    // ...
  }
})

Type-Safe Command Execution

With generated types, you can execute commands programmatically with full type safety:

import { createCLI } from '@bunli/core'

const cli = await createCLI(config)

// ✅ Fully type-safe!
await cli.execute('deploy', {
  env: 'production', // ← Autocomplete: 'dev' | 'staging' | 'prod'
  dryRun: true       // ← Type: boolean
})

// ❌ Type errors!
await cli.execute('deploy', {
  env: 'invalid',  // ❌ Error: Type '"invalid"' is not assignable
  dryRun: 'yes'    // ❌ Error: Type 'string' is not assignable to type 'boolean'
})

Command Discovery

Generated types provide helper functions for type-safe command discovery:

import { listCommands, getCommandApi, hasCommand } from './commands.gen'

// List all commands
const commands = listCommands()
console.log(`Total commands: ${commands.length}`)

// Type-safe command lookup
if (hasCommand('deploy')) {
  const deployApi = getCommandApi('deploy')
  // deployApi is fully typed!
}

Advanced Type Utilities

Bunli exports advanced TypeScript type utilities for complex type manipulation:

import { 
  UnionToIntersection, 
  MergeAll, 
  Expand,
  DeepPartial,
  Constrain
} from '@bunli/core'

// Get all command options as a union
type AllCommandOptions = UnionToIntersection<
  ReturnType<typeof getCommandApi>[keyof CommandRegistry]['options']
>

// Merge all command metadata
type AllCommands = MergeAll<
  Array<{ name: string; description: string }>
>

Configuration

Enable type generation in your bunli.config.ts:

export default defineConfig({
  name: 'my-cli',
  version: '1.0.0',
})

Generated File Structure

The .bunli/commands.gen.ts file contains:

  • CommandsByName interface - Maps command names to their definitions
  • RegisteredCommands module augmentation - Extends @bunli/core types
  • Helper functions - getCommandApi, listCommands, hasCommand
  • Runtime registry - For command discovery

See Also