Skip to content

Commit

Permalink
feat: Ability to create a shareable token by providing a key for its …
Browse files Browse the repository at this point in the history
…symbol
  • Loading branch information
mnasyrov committed Sep 8, 2023
1 parent a855014 commit 4fde1d9
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import {
createContainer,
FACTORIES_MAP,
FAKE_FACTORY,
optional,
PARENT_CONTAINER,
ResolverError,
token,
} from './ditox';
} from './container';
import {injectable} from './utils';
import {optional, token} from './tokens';

const NUMBER = token<number>('number');
const STRING = token<string>('string');
Expand Down Expand Up @@ -595,5 +594,18 @@ describe('Container', () => {
expect(parent.resolve(optionalNumber)).toBe(1);
expect(container.resolve(optionalNumber)).toBe(1);
});

it('should resolve a value by shared tokens', () => {
const key = 'token-' + Date.now();
const t1 = token({key});
const t2 = token({key});
expect(t1).not.toBe(t2);

const container = createContainer();
container.bindValue(t1, 1);

expect(container.resolve(t1)).toBe(1);
expect(container.resolve(t2)).toBe(1);
});
});
});
56 changes: 1 addition & 55 deletions packages/ditox/src/ditox.ts → packages/ditox/src/container.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,4 @@
/**
* @ignore
* Binding token for mandatory value
*/
export type RequiredToken<T> = {
symbol: symbol;
type?: T; // Anchor for Typescript type inference.
isOptional?: false;
};

/**
* @ignore
* Binding token for optional value
*/
export type OptionalToken<T> = {
symbol: symbol;
type?: T; // Anchor for Typescript type inference.
isOptional: true;
optionalValue: T;
};

/**
* Binding token.
*/
export type Token<T> = RequiredToken<T> | OptionalToken<T>;

/**
* Creates a new binding token.
* @param description - Token description for better error messages.
*/
export function token<T>(description?: string): Token<T> {
return {symbol: Symbol(description)};
}

/**
* Decorate a token with an optional value.
* This value is be used as default value in case a container does not have registered token.
* @param token - Existed token.
* @param optionalValue - Default value for the resolver.
*/
export function optional<T>(
token: Token<T>,
optionalValue: T,
): OptionalToken<T>;
export function optional<T>(token: Token<T>): OptionalToken<T | undefined>;
export function optional<T>(
token: Token<T>,
optionalValue?: T,
): OptionalToken<T | undefined> {
return {
symbol: token.symbol,
isOptional: true,
optionalValue,
};
}
import {Token, token} from './tokens';

/**
* ResolverError is thrown by the resolver when a token is not found in a container.
Expand Down
14 changes: 5 additions & 9 deletions packages/ditox/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
export {token, optional, ResolverError, createContainer} from './ditox';
export type {
RequiredToken,
OptionalToken,
Token,
FactoryScope,
FactoryOptions,
Container,
} from './ditox';
export {token, optional} from './tokens';
export type {RequiredToken, OptionalToken, Token} from './tokens';

export {ResolverError, createContainer} from './container';
export type {FactoryScope, FactoryOptions, Container} from './container';

export {
isToken,
Expand Down
3 changes: 2 additions & 1 deletion packages/ditox/src/modules.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createContainer, token} from './ditox';
import {createContainer} from './container';
import {
bindModule,
bindModules,
Expand All @@ -8,6 +8,7 @@ import {
ModuleDeclaration,
} from './modules';
import {injectable} from './utils';
import {token} from './tokens';

describe('bindModule()', () => {
type TestQueries = {getValue: () => number};
Expand Down
3 changes: 2 additions & 1 deletion packages/ditox/src/modules.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Container, token, Token} from './ditox';
import {Container} from './container';
import {injectable} from './utils';
import {Token, token} from './tokens';

type AnyObject = Record<string, any>;
type EmptyObject = Record<string, never>;
Expand Down
54 changes: 54 additions & 0 deletions packages/ditox/src/tokens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {optional, token} from './tokens';

describe('token()', () => {
it('should return a token with description it is specified', () => {
expect(token().symbol.description).toBeUndefined();
expect(token('text1').symbol.description).toBe('text1');
expect(token({description: 'text2'}).symbol.description).toBe('text2');
});

it('should return independent tokens if key is not specified', () => {
const t1 = token();
const t2 = token();

expect(t1).not.toBe(t2);
expect(t1.symbol).not.toBe(t2.symbol);
expect(t1.isOptional).not.toBeTruthy();
});

it('should return tokens with the same symbol if key is specified', () => {
const source = token({key: 'test-token'});
const clone = token({key: 'test-token'});
const something = token({key: 'something-else'});

expect(source).not.toBe(clone);
expect(source.symbol).toBe(clone.symbol);

expect(something).not.toBe(source);
expect(something.symbol).not.toBe(source);
});
});

describe('optional()', () => {
it('should decorate a source token to attach an optional value', () => {
const t1 = token<number>();
expect(t1.isOptional).not.toBeTruthy();

const o1 = optional(t1);
expect(o1.symbol).toBe(t1.symbol);
expect(o1.isOptional).toBe(true);

const o2 = optional(t1, -1);
expect(o2.symbol).toBe(t1.symbol);
expect(o2.isOptional).toBe(true);
expect(o2.optionalValue).toBe(-1);
});

it('should reuse symbol from a source token ifits key is specified', () => {
const source = token({key: 'token-key'});
const clone = token({key: 'token-key'});
const optClone = optional(clone);

expect(optClone.symbol).toBe(source.symbol);
});
});
88 changes: 88 additions & 0 deletions packages/ditox/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @ignore
* Binding token for mandatory value
*/
export type RequiredToken<T> = {
symbol: symbol;
type?: T; // Anchor for Typescript type inference.
isOptional?: false;
};

/**
* @ignore
* Binding token for optional value
*/
export type OptionalToken<T> = {
symbol: symbol;
type?: T; // Anchor for Typescript type inference.
isOptional: true;
optionalValue: T;
};

/**
* Binding token
*/
export type Token<T> = RequiredToken<T> | OptionalToken<T>;

/**
* Token options
*/
export type TokenOptions =
| {
/**
* Key for token's symbol. It allows to create shareable tokens.
*/
key: string;

/** @ignore */
description?: undefined;
}
| {
/** Description for better error messages */
description?: string;

/** @ignore */
key?: undefined;
};

/**
* Creates a new binding token.
* @param description - Token description for better error messages.
*/
export function token<T>(description?: string): Token<T>;
/**
* Creates a new binding token.
* @param options - Token description for better error messages.
*/ export function token<T>(options?: TokenOptions): Token<T>;
export function token<T>(options?: TokenOptions | string): Token<T> {
const normalized: TokenOptions | undefined =
typeof options === 'string' ? {description: options} : options;

const symbol: symbol = normalized?.key
? Symbol.for(normalized.key)
: Symbol(normalized?.description);

return {symbol};
}

/**
* Decorate a token with an optional value.
* This value is be used as default value in case a container does not have registered token.
* @param token - Existed token.
* @param optionalValue - Default value for the resolver.
*/
export function optional<T>(
token: Token<T>,
optionalValue: T,
): OptionalToken<T>;
export function optional<T>(token: Token<T>): OptionalToken<T | undefined>;
export function optional<T>(
token: Token<T>,
optionalValue?: T,
): OptionalToken<T | undefined> {
return {
symbol: token.symbol,
isOptional: true,
optionalValue,
};
}
3 changes: 2 additions & 1 deletion packages/ditox/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createContainer, optional, ResolverError, token} from './ditox';
import {createContainer, ResolverError} from './container';
import {
bindMultiValue,
injectable,
Expand All @@ -8,6 +8,7 @@ import {
tryResolveValue,
tryResolveValues,
} from './utils';
import {optional, token} from './tokens';

const NUMBER = token<number>('number');
const STRING = token<string>('string');
Expand Down
3 changes: 2 additions & 1 deletion packages/ditox/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {Container, Token} from './ditox';
import type {Container} from './container';
import {Token} from './tokens';

type ValuesProps = {[key: string]: unknown};
type TokenProps<Props extends ValuesProps> = {
Expand Down

0 comments on commit 4fde1d9

Please sign in to comment.