Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save SuperOleg39/93e1aedbe01ed09a94a65851f9fb94ab to your computer and use it in GitHub Desktop.

Select an option

Save SuperOleg39/93e1aedbe01ed09a94a65851f9fb94ab to your computer and use it in GitHub Desktop.
Example code with token scope type checking
export const Scope = {
REQUEST: 'request' as const,
SINGLETON: 'singleton' as const,
};
export const Errors = {
NOT_FOUND: 'NotFound',
CIRCULAR_DEP: 'CircularDep',
REQUIRE_MULTI: 'RequireMulti',
MIXED_MULTI: 'MixedMulti',
WRONG_FORMAT: 'WrongFormat',
};
import type { Scope } from '../constant';
export interface TokenType<T> {
name: string;
options: TokenOptions;
isToken: true;
isModernToken: true;
toString(): string;
}
export interface TokenOptions {
multi?: boolean;
scope?: typeof Scope[keyof typeof Scope];
}
// import type { ScopeVariants } from '../Provider';
import type { Scope } from '../constant';
import type { TokenType, TokenOptions } from './createToken.h';
export class TokenClass<T> implements TokenType<T> {
/**
* Индетификатор токена
*/
name: string;
options: TokenOptions;
isToken: true = true;
// for potential breaking changes, not very useful at this moment
isModernToken: true = true;
constructor(name: string, options: TokenOptions = {}) {
this.name = name;
this.options = options;
}
/**
* toString будет использоваться для получения индитификатора токена
*/
toString() {
return this.name;
}
}
const BASE_TOKEN_TYPE = 'base token';
const MULTI_TOKEN_TYPE = 'multi token';
export type _BaseTokenRequestInterface = {
__type?: typeof BASE_TOKEN_TYPE;
__scope?: typeof Scope['REQUEST'];
};
export type _BaseTokenSingletonInterface = {
__type?: typeof BASE_TOKEN_TYPE;
__scope?: typeof Scope['SINGLETON'];
};
export type BaseTokenInterface<T = any> = T &
(_BaseTokenRequestInterface | _BaseTokenSingletonInterface);
export type MultiTokenInterface<T = any> = T & {
__type?: typeof MULTI_TOKEN_TYPE;
};
export type TokenInterface<T = any> = BaseTokenInterface<T> | MultiTokenInterface<T>;
export function createToken<Type = any>(
name: string,
options: { scope: typeof Scope['REQUEST'] }
): Type & _BaseTokenRequestInterface;
export function createToken<Type = any>(
name: string,
options: { scope: typeof Scope['SINGLETON'] }
): Type & _BaseTokenSingletonInterface;
export function createToken<Type = any>(
name: string,
options?: { scope?: typeof Scope[keyof typeof Scope] }
): BaseTokenInterface<Type>;
export function createToken<Type = any>(
name: string,
options: { multi: true; scope?: typeof Scope[keyof typeof Scope] }
): MultiTokenInterface<Type>;
export function createToken<T = any>(name: string, options?: TokenOptions): T {
return (new TokenClass<T>(name, options) as any) as T;
}
export type ExtractTokenType<
Token extends TokenInterface<unknown>
> = Token extends MultiTokenInterface<infer Type>
? Type[]
: Token extends BaseTokenInterface<infer Type>
? Type
: unknown;
export type OptionalTokenDependency<Type extends unknown> = {
token: TokenInterface<Type>;
optional: boolean;
};
import type {
TokenInterface,
MultiTokenInterface,
ExtractTokenType,
OptionalTokenDependency,
_BaseTokenSingletonInterface,
_BaseTokenRequestInterface,
} from './createToken/createToken';
type Provide = TokenInterface<unknown> | string | any;
export type ScopeVariants = 'request' | 'singleton';
type ProviderOptions = { token: Provide; optional?: boolean; multi?: boolean };
export type ProviderDep = Provide | OptionalTokenDependency<unknown> | ProviderOptions;
export type ProviderDeps = Record<string, ProviderDep>;
// если есть multi параметр, то это массив данных
type MultiEnhance<Option, Value> = Option extends true ? Value[] : Value;
// если есть optional параметр, то мы должны давать или значение или null. Работает только в strict режиме
type OptionalEnhance<Option, Value> = Option extends true ? Value | null : Value;
// обрабатываем options тип
export type OptionsType<OptionsToken, OptionsMulti, OptionsOptional> = OptionsToken extends string
? OptionalEnhance<OptionsOptional, MultiEnhance<OptionsMulti, any>>
: OptionalEnhance<OptionsOptional, MultiEnhance<OptionsMulti, OptionsToken>>;
// prettier-ignore
export type ProvideDepsIterator<T> = {
[P in keyof T]: T[P] extends OptionalTokenDependency<unknown>
? (ExtractTokenType<T[P]['token']> | null)
: T[P] extends TokenInterface<unknown>
? ExtractTokenType<T[P]>
: T[P] extends string
? any // строковые токены = any
: T[P] extends { token: infer OptionsToken; optional?: infer OptionsOptional, multi?: infer OptionsMulti }
? OptionsType<OptionsToken, OptionsMulti, OptionsOptional>
: T[P]; // Обычный токен
};
// prettier-ignore
type ClassCreator<Deps, P extends Provide = any> = new (
deps: ProvideDepsIterator<Deps>
) => P extends TokenInterface<unknown>
? P extends MultiTokenInterface<unknown>
? ExtractTokenType<P> | ExtractTokenType<P>[number]
: ExtractTokenType<P>
: P extends string
? any
: P;
// prettier-ignore
type FactoryCreator<Deps, P extends Provide = any> = (
deps: ProvideDepsIterator<FilterRequestScopeFromSingletonDeps<Deps, P>>
) => P extends TokenInterface<unknown>
? P extends MultiTokenInterface<unknown>
? ExtractTokenType<P> | ExtractTokenType<P>[number]
: ExtractTokenType<P>
: P extends string
? any
: P;
type CompileError<ErrorMessage extends any[]> = {
readonly __compileError: never;
};
// prettier-ignore
type FilterRequestScopeFromSingletonDeps<
Deps,
P extends Provide = any
> = P extends _BaseTokenSingletonInterface
? {
[Key in keyof Deps]: Deps[Key] extends _BaseTokenRequestInterface
? CompileError<["Dependency", Key ,"with scope: 'request' is not allowed in deps to scope: 'singleton' provider"]>
: Deps[Key] extends { token: _BaseTokenRequestInterface }
? CompileError<["Dependency", Key ,"with scope: 'request' is not allowed in deps to scope: 'singleton' provider"]>
: Deps[Key];
}
: Deps;
export interface FactoryProvider<Deps, P extends Provide = any> {
/**
* Идентификатор токена
*/
provide: P extends TokenInterface<unknown> ? P : Provide;
/**
* Тип регистрации провайдера, будет ли глобальный синглтон или инстанс для клиента
*/
scope?: ScopeVariants;
/**
* Список токенов которые нужны сервису. При получении зависимости
* эти зависимости будут проинициализованы и переданны в функцию/класс
*/
deps?: Deps;
/**
* Если true, то позволяет зарегистрировать несколько провайдеров на одном токен. Придет массив как значение
*/
multi?: boolean;
/**
* Функция которая будет вызвана при инициализации провайдера и получит зависимости, если они были заданы
*/
useFactory: FactoryCreator<Deps, P>;
}
export interface ClassProvider<Deps, P extends Provide = any> {
/**
* Идентификатор токена
*/
provide: P extends TokenInterface<unknown> ? P : Provide;
/**
* Тип регистрации провайдера, будет ли глобальный синглтон или инстанс для клиента
*/
scope?: ScopeVariants;
/**
* Список токенов которые нужны сервису. При получении зависимости
* эти зависимости будут проинициализованы и переданны в функцию/класс
*/
deps?: Deps;
/**
* Если true, то позволяет зарегистрировать несколько провайдеров на одном токен. Придет массив как значение
*/
multi?: boolean;
/**
* Класс который будет создана при инициализации провайдера и получит зависимости, если они были заданы
*/
useClass: ClassCreator<Deps, P>;
}
export interface ValueProvider<P extends Provide = any> {
/**
* Идентификатор токена
*/
provide: P extends TokenInterface<unknown> ? P : Provide;
/**
* Тип регистрации провайдера, будет ли глобальный синглтон или инстанс для клиента
*/
scope?: ScopeVariants;
/**
* Если true, то позволяет зарегистрировать несколько провайдеров на одном токен. Придет массив как значение
*/
multi?: boolean;
/**
* Простое значение, которое будет доступно
*/
// prettier-ignore
useValue: P extends TokenInterface<unknown>
? P extends MultiTokenInterface<unknown>
? ExtractTokenType<P> | ExtractTokenType<P>[number]
: ExtractTokenType<P>
: P extends string
? any
: P;
}
export type Provider<Deps = any, P extends Provide = any> =
| ValueProvider<P>
| ClassProvider<Deps, P>
| FactoryProvider<Deps, P>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment