diff --git a/README.md b/README.md index 57432c00..e2640e4a 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,28 @@ const unleashContext = { unleash.isEnabled('someToggle', unleashContext); ``` +## TypeScript: Custom Feature Toggle Names + +You can use TypeScript module augmentation to provide type safety for your feature toggle names. +This ensures that only specific feature names can be used with `isEnabled` and related methods, +catching typos at compile time. + +```typescript +import { initialize } from 'unleash-client'; + +declare module 'unleash-client' { + interface CustomTypeOptions { + name: 'aaa' | 'bbb'; + } +} + +const client = initialize({}); + +client.isEnabled('aaa'); +client.isEnabled('bbb'); +client.isEnabled('ccc'); // Error +``` + ## Advanced usage The initialize method takes the following arguments: diff --git a/src/client.ts b/src/client.ts index b8622047..f724f7aa 100644 --- a/src/client.ts +++ b/src/client.ts @@ -17,6 +17,23 @@ interface BooleanMap { [key: string]: boolean; } +/** + * Type for toggle name queries. Defaults to string but can be augmented by users. + * Users can extend this via module augmentation: + * + * declare module 'unleash-client' { + * interface CustomTypeOptions { + * name: string; + * } + * } + */ +export interface CustomTypeOptions {} + +type GetCustom = K extends keyof CustomTypeOptions + ? CustomTypeOptions[K] + : Fallback; +export type Name = GetCustom<'name', string>; + export default class UnleashClient extends EventEmitter { private repository: RepositoryInterface; @@ -106,7 +123,7 @@ export default class UnleashClient extends EventEmitter { }); } - isEnabled(name: string, context: Context, fallback: Function): boolean { + isEnabled(name: Name, context: Context, fallback: Function): boolean { const feature = this.repository.getToggle(name); const enabled = this.isFeatureEnabled(feature, context, fallback).enabled; diff --git a/src/index.ts b/src/index.ts index 5d7fd77a..c5d8602a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,12 +7,14 @@ import { UnleashEvents } from './events'; import { ClientFeaturesResponse } from './feature'; import InMemStorageProvider from './repository/storage-provider-in-mem'; import { UnleashConfig } from './unleash-config'; +import { CustomTypeOptions, Name } from './client'; // exports export { Strategy } from './strategy/index'; export { Context, Variant, PayloadType, Unleash, TagFilter, InMemStorageProvider, UnleashEvents }; export type { ClientFeaturesResponse, UnleashConfig }; export { UnleashMetricClient } from './impact-metrics/metric-client'; +export { CustomTypeOptions, Name }; let instance: undefined | Unleash; @@ -30,7 +32,7 @@ export async function startUnleash(options: UnleashConfig): Promise { return unleash; } -export function isEnabled(name: string, context: Context = {}, fallbackValue?: boolean): boolean { +export function isEnabled(name: Name, context: Context = {}, fallbackValue?: boolean): boolean { return instance ? instance.isEnabled(name, context, fallbackValue) : !!fallbackValue; } diff --git a/src/unleash.ts b/src/unleash.ts index e0497bc2..2a540e82 100644 --- a/src/unleash.ts +++ b/src/unleash.ts @@ -1,6 +1,6 @@ import { tmpdir } from 'os'; import { EventEmitter } from 'events'; -import Client from './client'; +import Client, { Name } from './client'; import Repository, { RepositoryInterface } from './repository'; import Metrics from './metrics'; import { Context } from './context'; @@ -270,9 +270,9 @@ export class Unleash extends EventEmitter { Unleash.instanceCount--; } - isEnabled(name: string, context?: Context, fallbackFunction?: FallbackFunction): boolean; - isEnabled(name: string, context?: Context, fallbackValue?: boolean): boolean; - isEnabled(name: string, context: Context = {}, fallback?: FallbackFunction | boolean): boolean { + isEnabled(name: Name, context?: Context, fallbackFunction?: FallbackFunction): boolean; + isEnabled(name: Name, context?: Context, fallbackValue?: boolean): boolean; + isEnabled(name: Name, context: Context = {}, fallback?: FallbackFunction | boolean): boolean { const enhancedContext = { ...this.staticContext, ...context }; const fallbackFunc = createFallbackFunction(name, enhancedContext, fallback);