Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 18 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 PropertyKey, Fallback> = K extends keyof CustomTypeOptions
? CustomTypeOptions[K]
: Fallback;
export type Name = GetCustom<'name', string>;

export default class UnleashClient extends EventEmitter {
private repository: RepositoryInterface;

Expand Down Expand Up @@ -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;

Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,7 +32,7 @@ export async function startUnleash(options: UnleashConfig): Promise<Unleash> {
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;
}

Expand Down
8 changes: 4 additions & 4 deletions src/unleash.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);

Expand Down