Skip to content

Add TConfig generic parameter to createCLI for type-safe custom config #152

Description

@vlavrynovych

Problem

The Firebase adapter team has FirebaseConfig extends Config, but createCLI doesn't support config generics. This forces type casting in the extendFlags callback, making it inconvenient and error-prone.

Current code (Firebase adapter):

interface FirebaseConfig extends Config {
  databaseUrl: string;
  applicationCredentials: string;
  connectionTimeout?: number;
}

const program = createCLI({
  addCustomOptions: (program) => {
    program.option('--database-url <url>', 'Firebase Database URL');
  },
  
  extendFlags: (config, flags) => {
    // ❌ config is typed as Config, not FirebaseConfig
    // Must cast to access FirebaseConfig properties
    (config as FirebaseConfig).databaseUrl = flags.databaseUrl;
    (config as FirebaseConfig).applicationCredentials = flags.credentials;
  },
  
  createExecutor: async (config) => {
    // ❌ config is typed as Config, not FirebaseConfig
    return FirebaseAdapter.getInstance({ config: config as FirebaseConfig });
  }
});

Proposed Solution

Add TConfig generic parameter to createCLI (same pattern as THandler in #138 and TConfig in #144).

After fix:

const program = createCLI<IFirebaseDB, FirebaseAdapter, FirebaseConfig>({
  addCustomOptions: (program) => {
    program.option('--database-url <url>', 'Firebase Database URL');
  },
  
  extendFlags: (config, flags) => {
    // ✅ config is typed as FirebaseConfig - full IntelliSense!
    config.databaseUrl = flags.databaseUrl;
    config.applicationCredentials = flags.credentials;
    config.connectionTimeout = flags.timeout ? parseInt(flags.timeout, 10) : undefined;
  },
  
  createExecutor: async (config) => {
    // ✅ config is typed as FirebaseConfig
    return FirebaseAdapter.getInstance({ config });
  }
});

Implementation

1. Update CLIOptions interface (createCLI.ts)

export interface CLIOptions<
  DB extends IDB,
  TExecutor extends MigrationScriptExecutor<DB> = MigrationScriptExecutor<DB>,
  TConfig extends Config = Config  // NEW: Add TConfig generic
> {
  createExecutor: (config: TConfig) => TExecutor | Promise<TExecutor>;
  
  config?: Partial<TConfig>;  // Update type
  
  configLoader?: IConfigLoader<TConfig>;  // Already exists from #144
  
  extendFlags?: (config: TConfig, flags: CLIFlags) => void;  // Update type
  
  // ... other fields unchanged
}

2. Update createCLI function signature

export function createCLI<
  DB extends IDB,
  TExecutor extends MigrationScriptExecutor<DB> = MigrationScriptExecutor<DB>,
  TConfig extends Config = Config  // NEW: Add TConfig generic
>(
  options: CLIOptions<DB, TExecutor, TConfig>
): Command {
  // Implementation stays mostly the same
  // TypeScript will infer TConfig throughout
}

3. Update internal usage

The createExecutorWithFlags factory function already uses options.configLoader which is typed as IConfigLoader<TConfig> from #144, so it should work seamlessly.


Benefits


Testing

Add tests to test/unit/cli/createCLI.test.ts:

  • Test with custom config type (e.g., FirebaseConfig)
  • Verify extendFlags receives typed config
  • Verify createExecutor receives typed config
  • Ensure backward compatibility (no generic = Config)

Related Issues

This completes the type safety story started in v0.8.0-v0.8.3.


Acceptance Criteria

  • CLIOptions has TConfig generic parameter with Config default
  • createCLI has TConfig generic parameter with Config default
  • extendFlags callback receives TConfig (not Config)
  • createExecutor callback receives TConfig (not Config)
  • options.config typed as Partial<TConfig>
  • All existing tests pass (backward compatibility)
  • New tests added for custom config type
  • 100% test coverage maintained
  • Build succeeds with no TypeScript errors
  • Documentation updated with Firebase example

Metadata

Metadata

Assignees

Labels

⚡ Priority: HighImportant tasks requiring quick attention✨ Type: FeatureNew functionality or enhancement🟢 Test: LowMinimal testing effort needed

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions