Skip to main content

Watch Mode

Watch mode automatically regenerates your TypeScript client whenever your OpenAPI specification changes. This is particularly useful during development when your API is evolving.

Overview

Watch mode polls your OpenAPI specification at regular intervals and regenerates the client when changes are detected. It works with both local files and remote URLs.
Watch mode is designed for remote specifications accessed via HTTP/HTTPS. For local file watching, consider using a file system watcher like nodemon or chokidar instead.

Configuration

Watch mode is configured through the input.watch property. The implementation is in /home/daytona/workspace/source/packages/shared/src/config/input/types.ts:175:
export type Watch = {
  /**
   * Whether this feature is enabled.
   *
   * @default false
   */
  enabled?: boolean;
  /**
   * How often should we attempt to detect the input file change? (in ms)
   *
   * @default 1000
   */
  interval?: number;
  /**
   * How long will we wait before the request times out?
   *
   * @default 60_000
   */
  timeout?: number;
};

Basic Usage

CLI

Enable watch mode with the -w or --watch flag:
# Enable with default 1000ms interval
openapi-ts -i https://api.example.com/openapi.json -o ./src/client -w

# Specify custom interval (2000ms)
openapi-ts -i https://api.example.com/openapi.json -o ./src/client -w 2000

Configuration File

import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: {
    path: 'https://api.example.com/openapi.json',
    watch: true, // Enable with defaults
  },
  output: 'src/client',
});

Configuration Options

enabled

Type: boolean
Default: false
Enables or disables watch mode.
watch: {
  enabled: true,
}

interval

Type: number (milliseconds)
Default: 1000
How frequently to check for changes. The watcher polls the specification URL at this interval.
watch: {
  enabled: true,
  interval: 5000, // Check every 5 seconds
}
Setting a very low interval (< 100ms) can cause excessive network requests and may trigger rate limiting on some APIs.

timeout

Type: number (milliseconds)
Default: 60_000 (60 seconds)
How long to wait for the specification to download before timing out.
watch: {
  enabled: true,
  timeout: 30_000, // 30 second timeout for slow APIs
}

Multiple Inputs

Configure watch mode independently for each input:
import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: [
    {
      path: 'https://api.example.com/v1/openapi.json',
      watch: true, // Watch this one
    },
    {
      path: 'https://api.example.com/v2/openapi.json',
      watch: false, // Don't watch this one
    },
  ],
  output: 'src/client',
});

Global Watch Configuration

Apply watch settings to all inputs:
import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: [
    'https://api.example.com/v1/openapi.json',
    'https://api.example.com/v2/openapi.json',
  ],
  output: 'src/client',
  // Global watch configuration
  watch: {
    enabled: true,
    interval: 3000,
  },
});

How It Works

The watch mode implementation uses HTTP polling with conditional requests. From /home/daytona/workspace/source/packages/openapi-ts/src/createClient.ts:169:
const watchedInput = config.input.find(
  (input, index) => 
    input.watch.enabled && 
    typeof inputPaths[index]!.path === 'string',
);

if (watchedInput) {
  setTimeout(() => {
    createClient({
      config,
      dependencies,
      jobIndex,
      logger,
      watches, // Passes headers for conditional requests
    });
  }, watchedInput.watch.interval);
}

Conditional Requests

Watch mode uses HTTP headers to avoid unnecessary regeneration:
  • ETag - Cached entity tags
  • Last-Modified - Cached modification times
  • If-None-Match - Conditional request headers
  • If-Modified-Since - Conditional request headers
If the server responds with 304 Not Modified, regeneration is skipped.

Authentication

For APIs requiring authentication, pass credentials via fetch options:
import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: {
    path: 'https://api.example.com/openapi.json',
    watch: true,
    fetch: {
      headers: {
        'Authorization': `Bearer ${process.env.API_TOKEN}`,
      },
    },
  },
  output: 'src/client',
});

Programmatic Usage

When using createClient programmatically, watch mode runs indefinitely:
import { createClient } from '@hey-api/openapi-ts';

// This will run forever in watch mode
await createClient({
  input: {
    path: 'https://api.example.com/openapi.json',
    watch: true,
  },
  output: 'src/client',
});

// This code will never execute
console.log('Done!');

Detecting Watch Mode

Check if watch mode is active:
import { createClient } from '@hey-api/openapi-ts';

const contexts = await createClient({
  input: {
    path: 'https://api.example.com/openapi.json',
    watch: process.env.WATCH === 'true',
  },
  output: 'src/client',
});

const hasActiveWatch = contexts[0]?.config.input.some(
  (input) => input.watch?.enabled
);

if (!hasActiveWatch) {
  console.log('Generation complete!');
  process.exit(0);
} else {
  console.log('Watching for changes...');
}

Development Workflow

Package Scripts

Add watch mode to your development workflow:
package.json
{
  "scripts": {
    "generate": "openapi-ts -c openapi-ts.config.ts",
    "generate:watch": "openapi-ts -c openapi-ts.config.ts -w",
    "dev": "npm run generate:watch & next dev"
  }
}

Docker Compose

Watch a specification served by Docker:
docker-compose.yml
services:
  api:
    image: my-api:latest
    ports:
      - "3000:3000"
  
  codegen:
    image: node:20
    working_dir: /app
    volumes:
      - ./:/app
    command: npm run generate:watch
    environment:
      - OPENAPI_URL=http://api:3000/openapi.json

Error Handling

Watch mode handles errors gracefully to avoid crashing during development:
// From /home/daytona/workspace/source/packages/openapi-ts/src/createClient.ts:64
if (error && !_watches) {
  // Throw on first run
  const text = await response.text().catch(() => '');
  throw new Error(
    `Request failed with status ${response.status}: ${text || response.statusText}`,
  );
}

// Subsequent errors in watch mode are logged but don't throw
// This allows the watcher to recover when the server comes back online

Handling Transient Errors

If your API server restarts, watch mode will continue polling:
import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
  input: {
    path: 'http://localhost:3000/openapi.json',
    watch: {
      enabled: true,
      interval: 2000,
      timeout: 5000, // Shorter timeout for local development
    },
  },
  output: 'src/client',
  logs: {
    level: 'info', // Log when regeneration occurs
  },
});

Performance Considerations

1
Choose Appropriate Intervals
2
Balance responsiveness with resource usage:
3
  • Development: 1000-3000ms is reasonable
  • CI/CD: Don’t use watch mode
  • Production: Never use watch mode
  • 4
    Use Conditional Requests
    5
    Ensure your API server supports ETag and Last-Modified headers to minimize unnecessary regeneration.
    6
    Optimize Network Usage
    7
    For large specifications, increase the interval:
    8
    watch: {
      enabled: true,
      interval: 5000, // Less frequent for large specs
    }
    
    9
    Clear Console Output
    10
    Watch mode clears the console on regeneration (from /home/daytona/workspace/source/packages/openapi-ts/src/createClient.ts:101):
    11
    if (config.logs.level !== 'silent' && _watches) {
      console.clear();
      // ... log input paths
    }
    

    Limitations

    Watch mode has several limitations:
    1. Remote URLs only - Designed for HTTP/HTTPS specifications
    2. Polling-based - Not as efficient as file system watching
    3. Single process - Runs in the foreground
    4. No file system events - Won’t detect local file changes

    Local File Watching

    For local files, use a file system watcher instead:
    # Using nodemon
    nodemon --watch openapi.json --exec "openapi-ts -c openapi-ts.config.ts"
    
    # Using chokidar-cli
    chokidar 'openapi.json' -c "openapi-ts -c openapi-ts.config.ts"
    
    Or create a custom watcher:
    watch.ts
    import { watch } from 'node:fs';
    import { createClient } from '@hey-api/openapi-ts';
    
    watch('openapi.json', async (eventType) => {
      if (eventType === 'change') {
        console.log('Regenerating client...');
        await createClient({
          input: 'openapi.json',
          output: 'src/client',
        });
      }
    });
    

    Next Steps

    Programmatic Usage

    Learn how to use the createClient API programmatically

    Custom Plugins

    Build custom plugins to extend code generation

    Build docs developers (and LLMs) love