ditox 3.3.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/index.cjs +19 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +19 -6
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -3
- package/src/container.test.ts +43 -0
- package/src/container.ts +24 -9
- package/src/modules.test.ts +18 -0
- package/src/removal-compatibility.test.ts +259 -0
- package/src/removal.test.ts +190 -0
package/README.md
CHANGED
|
@@ -132,6 +132,33 @@ container.remove(LOGGER_TOKEN);
|
|
|
132
132
|
container.removeAll();
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
### Errors during bulk cleanup
|
|
136
|
+
|
|
137
|
+
`removeAll()` continues visiting factories after an `onRemoved` callback or a
|
|
138
|
+
cleanup options accessor throws. It clears local bindings and restores internal
|
|
139
|
+
tokens before rethrowing the first thrown value unchanged, including non-`Error`
|
|
140
|
+
values. Later errors are discarded. The container can be reused after cleanup;
|
|
141
|
+
a subsequent `removeAll()` does not retry callbacks for the cleared bindings.
|
|
142
|
+
|
|
143
|
+
Bindings remain available to callbacks until explicitly removed or cleared at
|
|
144
|
+
the end. Cleanup does not resolve unused factories or dispose transient values
|
|
145
|
+
or values owned by a parent container. A failing callback must handle its own
|
|
146
|
+
internal cleanup; the container cannot resume execution inside it.
|
|
147
|
+
|
|
148
|
+
`remove(token)` retains its existing behavior: if its callback throws, the binding
|
|
149
|
+
remains and removal can be retried. This also applies to single-module removal
|
|
150
|
+
when `destroy()` throws.
|
|
151
|
+
|
|
152
|
+
Reentrant removal and mutation retain the existing live traversal behavior.
|
|
153
|
+
Nested removals run immediately and can invoke a callback more than once.
|
|
154
|
+
New factory entries appended during traversal can be visited. Replacing an
|
|
155
|
+
existing entry without deleting it does not revisit an already visited key;
|
|
156
|
+
deleting and reinserting it appends a new entry that can be visited again.
|
|
157
|
+
This fix does not add recursion guards or guarantee cleanup of every value
|
|
158
|
+
created during callbacks. Callbacks must
|
|
159
|
+
avoid unbounded recursive removal or endlessly adding bindings. Cleanup remains
|
|
160
|
+
synchronous; returned promises are not awaited.
|
|
161
|
+
|
|
135
162
|
## Container Hierarchy
|
|
136
163
|
|
|
137
164
|
Ditox.js supports a "parent-child" hierarchy. If the child container cannot
|
package/dist/index.cjs
CHANGED
|
@@ -82,12 +82,25 @@ function createContainer(parentArg) {
|
|
|
82
82
|
factories.delete(token.symbol);
|
|
83
83
|
},
|
|
84
84
|
removeAll() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
let hasError = false;
|
|
86
|
+
let firstError;
|
|
87
|
+
try {
|
|
88
|
+
factories.forEach((context, tokenSymbol) => {
|
|
89
|
+
try {
|
|
90
|
+
if (context.options) executeOnRemoved(tokenSymbol, context.options);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (!hasError) {
|
|
93
|
+
hasError = true;
|
|
94
|
+
firstError = error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} finally {
|
|
99
|
+
values.clear();
|
|
100
|
+
factories.clear();
|
|
101
|
+
bindInternalTokens();
|
|
102
|
+
}
|
|
103
|
+
if (hasError) throw firstError;
|
|
91
104
|
},
|
|
92
105
|
hasToken(token) {
|
|
93
106
|
return values.has(token.symbol) || factories.has(token.symbol) || parentContainersHaveToken(parents, token);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/tokens.ts","../src/container.ts","../src/utils.ts","../src/modules.ts"],"sourcesContent":["/**\n * @ignore\n * Binding token for mandatory value\n */\nexport type RequiredToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional?: false;\n};\n\n/**\n * @ignore\n * Binding token for optional value\n */\nexport type OptionalToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional: true;\n optionalValue: T;\n};\n\n/**\n * Binding token\n */\nexport type Token<T> = RequiredToken<T> | OptionalToken<T>;\n\n/**\n * Token options\n */\nexport type TokenOptions =\n | {\n /**\n * Key for token's symbol. It allows to create shareable tokens.\n */\n key: string;\n\n /** @ignore */\n description?: undefined;\n }\n | {\n /** Description for better error messages */\n description?: string;\n\n /** @ignore */\n key?: undefined;\n };\n\n/**\n * Creates a new binding token.\n * @param description - Token description for better error messages.\n */\nexport function token<T>(description?: string): Token<T>;\n/**\n * Creates a new binding token.\n * @param options - Token description for better error messages.\n */ export function token<T>(options?: TokenOptions): Token<T>;\nexport function token<T>(options?: TokenOptions | string): Token<T> {\n const normalized: TokenOptions | undefined =\n typeof options === 'string' ? { description: options } : options;\n\n const symbol: symbol = normalized?.key\n ? Symbol.for(normalized.key)\n : Symbol(normalized?.description);\n\n return { symbol };\n}\n\n/**\n * Decorate a token with an optional value.\n * This value is be used as default value in case a container does not have registered token.\n * @param token - Existed token.\n * @param optionalValue - Default value for the resolver.\n */\nexport function optional<T>(\n token: Token<T>,\n optionalValue: T,\n): OptionalToken<T>;\nexport function optional<T>(token: Token<T>): OptionalToken<T | undefined>;\nexport function optional<T>(\n token: Token<T>,\n optionalValue?: T,\n): OptionalToken<T | undefined> {\n return {\n symbol: token.symbol,\n isOptional: true,\n optionalValue,\n };\n}\n","import { Token, token } from './tokens';\n\n/**\n * ResolverError is thrown by the resolver when a token is not found in a container.\n */\nexport class ResolverError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = 'ResolverError';\n }\n}\n\n/**\n * @see https://github.com/mnasyrov/ditox#factory-lifetimes\n */\nexport type FactoryScope = 'scoped' | 'singleton' | 'transient';\n\n/**\n * Options for factory binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n * - `transient` - The value is created every time it is resolved.\n *\n * `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.\n */\nexport type FactoryOptions<T> =\n | {\n scope?: 'scoped' | 'singleton';\n onRemoved?: (value: T) => void;\n }\n | {\n scope: 'transient';\n };\n\n/**\n * Dependency container.\n */\nexport type Container = {\n /**\n * Binds a value for the token\n */\n bindValue<T>(token: Token<T>, value: T): void;\n\n /**\n * Binds a factory for the token.\n */\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void;\n\n /**\n * Checks if the token is registered in the container hierarchy.\n */\n hasToken(token: Token<unknown>): boolean;\n\n /**\n * Returns a resolved value by the token, or returns `undefined` in case the token is not found.\n */\n get<T>(token: Token<T>): T | undefined;\n\n /**\n * Returns a resolved value by the token or throws `ResolverError` in case the token is not found.\n */\n resolve<T>(token: Token<T>): T;\n\n /**\n * Removes a binding for the token.\n */\n remove<T>(token: Token<T>): void;\n\n /**\n * Removes all bindings in the container.\n */\n removeAll(): void;\n};\n\n/**\n * A subset of Container interface that provides read-only access to dependency resolution.\n * This type is used for parent containers to allow token resolution without exposing mutation methods.\n */\nexport type ContainerResolver = Pick<Container, 'hasToken' | 'get' | 'resolve'>;\n\n/** @internal */\nexport const CONTAINER: Token<Container> = token('ditox.Container');\n/** @internal */\nexport const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(\n 'ditox.ParentContainer',\n);\n/** @internal */\nexport const RESOLVER: Token<Resolver> = token('ditox.Resolver');\n\n/** @internal */\nconst NOT_FOUND = Symbol();\n\n/** @internal */\nconst DEFAULT_SCOPE: FactoryScope = 'singleton';\n\n/** @internal */\ntype FactoryContext<T> = {\n factory: (container: Container) => T;\n options?: FactoryOptions<T>;\n};\n\n/** @internal */\ntype ValuesMap = Map<symbol, any>;\n\n/** @internal */\nexport type FactoriesMap = Map<symbol, FactoryContext<any>>;\n\n/** @internal */\nexport const FACTORIES_MAP: Token<FactoriesMap> = token('ditox.FactoriesMap');\n\n/** @internal */\ntype Resolver = <T>(token: Token<T>, origin: Container) => T | typeof NOT_FOUND;\n\n/** @internal */\nfunction getScope<T>(options?: FactoryOptions<T>): FactoryScope {\n return options?.scope ?? DEFAULT_SCOPE;\n}\n\n/** @internal */\nfunction getOnRemoved<T>(options: FactoryOptions<T>) {\n return options.scope === undefined ||\n options.scope === 'scoped' ||\n options.scope === 'singleton'\n ? options.onRemoved\n : undefined;\n}\n\n/** @internal */\nfunction isInternalToken<T>(token: Token<T>): boolean {\n return (\n token.symbol === CONTAINER.symbol ||\n token.symbol === PARENT_CONTAINERS.symbol ||\n token.symbol === RESOLVER.symbol\n );\n}\n\n/**\n * Creates a new dependency container.\n *\n * Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.\n *\n * @param parentArg - Optional parent container or an array of containers.\n */\nexport function createContainer(\n parentArg?: ContainerResolver | ReadonlyArray<ContainerResolver>,\n): Container {\n const parents: ReadonlyArray<ContainerResolver> | undefined = parentArg\n ? Array.isArray(parentArg)\n ? [...parentArg]\n : [parentArg]\n : undefined;\n\n const values: ValuesMap = new Map<symbol, any>();\n const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();\n\n const container: Container = {\n bindValue<T>(token: Token<T>, value: T): void {\n if (isInternalToken(token)) {\n return;\n }\n\n values.set(token.symbol, value);\n },\n\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void {\n if (isInternalToken(token)) {\n return;\n }\n\n factories.set(token.symbol, { factory, options });\n },\n\n remove<T>(token: Token<T>): void {\n if (isInternalToken(token)) {\n return;\n }\n\n const options = factories.get(token.symbol)?.options;\n if (options) {\n executeOnRemoved(token.symbol, options);\n }\n\n values.delete(token.symbol);\n factories.delete(token.symbol);\n },\n\n removeAll(): void {\n factories.forEach((context, tokenSymbol) => {\n if (context.options) {\n executeOnRemoved(tokenSymbol, context.options);\n }\n });\n\n values.clear();\n factories.clear();\n bindInternalTokens();\n },\n\n hasToken(token: Token<unknown>): boolean {\n return (\n values.has(token.symbol) ||\n factories.has(token.symbol) ||\n parentContainersHaveToken(parents, token)\n );\n },\n\n get<T>(token: Token<T>): T | undefined {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n return undefined;\n },\n\n resolve<T>(token: Token<T>): T {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n throw new ResolverError(\n `Token \"${token.symbol.description ?? ''}\" is not provided`,\n );\n },\n };\n\n function resolver<T>(\n token: Token<T>,\n origin: Container,\n ): T | typeof NOT_FOUND {\n const value = values.get(token.symbol);\n const hasValue = value !== undefined || values.has(token.symbol);\n\n if (hasValue && origin === container) {\n return value;\n }\n\n const factoryContext = factories.get(token.symbol);\n if (factoryContext) {\n const scope = getScope(factoryContext.options);\n\n switch (scope) {\n case 'singleton': {\n if (hasValue) {\n return value;\n } else if (parentContainersHaveToken(parents, token)) {\n break;\n } else {\n // Cache the value in the same container where the factory is registered.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'scoped': {\n if (hasValue) {\n return value;\n } else {\n // Create a value within the factory's container and cache it.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'transient': {\n // Create a value within the origin container and don't cache it.\n return factoryContext.factory(origin);\n }\n }\n }\n\n if (hasValue) {\n return value;\n }\n\n if (parents) {\n return parentContainersResolveToken(parents, token, origin);\n }\n\n return NOT_FOUND;\n }\n\n function executeOnRemoved<T>(\n tokenSymbol: symbol,\n options: FactoryOptions<T>,\n ) {\n const onRemoved = getOnRemoved(options);\n if (onRemoved) {\n const value = values.get(tokenSymbol);\n if (value !== undefined || values.has(tokenSymbol)) {\n onRemoved(value);\n }\n }\n }\n\n function bindInternalTokens() {\n values.set(CONTAINER.symbol, container);\n values.set(RESOLVER.symbol, resolver);\n values.set(FACTORIES_MAP.symbol, factories);\n\n if (parents) {\n values.set(PARENT_CONTAINERS.symbol, parents);\n }\n }\n\n bindInternalTokens();\n return container;\n}\n\nfunction parentContainersHaveToken<T>(\n parents: ReadonlyArray<ContainerResolver> | undefined,\n token: Token<T>,\n): boolean {\n if (!parents) return false;\n\n for (let i = 0; i < parents.length; i++) {\n if (parents[i].hasToken(token)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parentContainersResolveToken<T>(\n parents: ReadonlyArray<ContainerResolver>,\n token: Token<T>,\n origin: Container,\n): T | typeof NOT_FOUND {\n for (const parentContainer of parents) {\n const parentResolver = parentContainer.get(RESOLVER);\n if (parentResolver) {\n const resolved = parentResolver(token, origin);\n if (resolved !== NOT_FOUND) {\n return resolved;\n }\n }\n }\n\n return NOT_FOUND;\n}\n","import type { Container } from './container';\nimport { Token } from './tokens';\n\ntype ValuesProps = { [key: string]: unknown };\ntype TokenProps<Props extends ValuesProps> = {\n [K in keyof Props]: Token<Props[K]>;\n};\n\n/**\n * Checks if a value is the token\n */\nexport function isToken<T>(value: unknown): value is Token<T> {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n 'symbol' in value &&\n typeof (value as any).symbol === 'symbol'\n );\n}\n\n/**\n * Rebinds the array by the token with added new value.\n * @param container - Dependency container.\n * @param token - Token for an array of values.\n * @param value - New value which is added to the end of the array.\n */\nexport function bindMultiValue<T>(\n container: Container,\n token: Token<ReadonlyArray<T>>,\n value: T,\n): void {\n const prevValues = container.get(token) ?? [];\n const nextValues = [...prevValues, value];\n container.bindValue(token, nextValues);\n}\n\n/**\n * Tries to resolve a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const value = tryResolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = tryResolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function tryResolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V | undefined\n : Tokens extends TokenProps<infer Props>\n ? Partial<Props>\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.get(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach((key) => (obj[key] = container.get(token[key])));\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const items1 = tryResolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = tryResolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function tryResolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V | undefined\n : Tokens[K] extends TokenProps<infer Props>\n ? Partial<Props>\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => tryResolveValue(container, item)) as Values;\n}\n\n/**\n * Resolves a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a value is not found by the token, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const value = resolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = resolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function resolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V\n : Tokens extends TokenProps<infer Props>\n ? Props\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.resolve(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach(\n (key) => (obj[key] = container.resolve(token[key])),\n );\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const items1 = resolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = resolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function resolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => resolveValue(container, item)) as Values;\n}\n\n/**\n * Decorates a factory by passing resolved values as factory arguments.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param factory - A factory.\n * @param tokens - Tokens which correspond to factory arguments.\n *\n * @return Decorated factory which takes a dependency container as a single argument.\n */\nexport function injectable<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n factory: (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return (container) => {\n const values: Values = resolveValues(container, ...tokens);\n return factory.apply(this, values);\n };\n}\n\n/**\n * Decorates a class by passing resolved values as arguments to its constructor.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param constructor - Constructor of a class\n * @param tokens - Tokens which correspond to constructor arguments\n *\n * @return A factory function which takes a dependency container as a single argument\n * and returns a new created class.\n */\nexport function injectableClass<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n constructor: new (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return injectable<Tokens, Values, Result>(\n (...values) => new constructor(...values),\n ...tokens,\n );\n}\n","import { Container } from './container';\nimport { Token, token } from './tokens';\nimport { injectable } from './utils';\n\ntype AnyObject = Record<string, any>;\ntype EmptyObject = Record<string, never>;\n\ntype ModuleController = {\n /** Dispose the module and clean its resources */\n destroy?: () => void;\n};\n\n/**\n * Dependency module\n *\n * @example\n * ```ts\n * type LoggerModule = Module<{\n * logger: Logger;\n * }>;\n * ```\n */\nexport type Module<ModuleProps extends AnyObject = EmptyObject> =\n ModuleController & ModuleProps;\n\ntype GetModuleProps<T> = T extends Module<infer Props> ? Props : never;\n\n/**\n * Description how to bind the module in declarative way.\n *\n * @example\n * ```ts\n * const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {\n * token: LOGGER_MODULE_TOKEN,\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * };\n * ```\n */\nexport type ModuleDeclaration<T extends Module<AnyObject>> = {\n /** Token for the module */\n token: Token<T>;\n\n /** Modules for binding */\n imports?: ReadonlyArray<ModuleBindingEntry>;\n\n /** Factory of the module */\n factory: (container: Container) => T;\n\n /** Dictionary of module properties which are bound to tokens. */\n exports?: {\n [K in keyof GetModuleProps<T>]?: Token<GetModuleProps<T>[K]>;\n };\n\n /** Callback could be used to prepare an environment. It is called before binding the module. */\n beforeBinding?: (container: Container) => void;\n\n /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */\n afterBinding?: (container: Container) => void;\n\n /**\n * Strategy for executing the factory:\n * - `lazy` - **This is the default**. The factory is called when the module is resolved.\n * - `eager` - The factory is called immediately after the module is bound to the container.\n */\n strategy?: 'eager' | 'lazy';\n};\n\nexport type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;\n\n/**\n * Options for module binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n */\nexport type BindModuleOptions = {\n scope?: 'scoped' | 'singleton';\n};\n\ntype ModuleDeclarationWithOptions = {\n module: ModuleDeclaration<AnyObject>;\n options: BindModuleOptions;\n};\n\nexport type ModuleBindingEntry =\n | ModuleDeclaration<AnyObject>\n | ModuleDeclarationWithOptions;\n\n/**\n * Binds the dependency module to the container\n * @param container - Dependency container.\n * @param moduleDeclaration - Declaration of the dependency module.\n * @param options - Options for module binding.\n *\n * @example\n * ```ts\n * bindModule(container, LOGGER_MODULE);\n * ```\n */\nexport function bindModule<T extends Module<AnyObject>>(\n container: Container,\n moduleDeclaration: ModuleDeclaration<T>,\n options?: BindModuleOptions,\n): void {\n const rootEntry: ModuleBindingEntry = {\n module: moduleDeclaration,\n options: options ?? {},\n };\n\n const bfsVisits = new Set<ModuleBindingEntry>([rootEntry]);\n const bfsQueue: ModuleBindingEntry[] = [rootEntry];\n\n let bfsIndex = 0;\n while (bfsIndex < bfsQueue.length) {\n const entry = bfsQueue[bfsIndex];\n\n const m = 'module' in entry ? entry.module : entry;\n\n m.imports?.forEach((depEntry) => {\n if (!bfsVisits.has(depEntry)) {\n bfsVisits.add(depEntry);\n bfsQueue.push(depEntry);\n }\n });\n\n bfsIndex++;\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.beforeBinding?.(container);\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n bindModuleEntry(container, entry);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.afterBinding?.(container);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n if (m.strategy === 'eager') {\n container.resolve(m.token);\n }\n }\n}\n\nfunction bindModuleEntry(\n container: Container,\n entry: ModuleBindingEntry,\n): void {\n let module: ModuleDeclaration<AnyObject>;\n let options: BindModuleOptions | undefined;\n\n if ('module' in entry) {\n module = entry.module;\n options = entry.options;\n } else {\n module = entry;\n }\n\n const scope = options?.scope;\n const exportedValueTokens = new Set<Token<unknown>>();\n const moduleExports = module.exports;\n\n if (moduleExports) {\n const keys = Object.keys(moduleExports);\n\n keys.forEach((valueKey) => {\n const valueToken = moduleExports[valueKey];\n if (valueToken) {\n exportedValueTokens.add(valueToken);\n\n container.bindFactory(\n valueToken,\n injectable((module) => module[valueKey], module.token),\n { scope },\n );\n }\n });\n }\n\n container.bindFactory(module.token, module.factory, {\n scope,\n onRemoved: (moduleInstance) => {\n if (moduleInstance.destroy) {\n moduleInstance.destroy();\n }\n\n exportedValueTokens.forEach((valueToken) => container.remove(valueToken));\n exportedValueTokens.clear();\n },\n });\n}\n\n/**\n * Binds dependency modules to the container\n *\n * @param container - Dependency container for binding\n * @param modules - Array of module binding entries: module declaration or `{module: ModuleDeclaration, options: BindModuleOptions}` objects.\n */\nexport function bindModules(\n container: Container,\n modules: ReadonlyArray<ModuleBindingEntry>,\n): void {\n modules.forEach((entry) => {\n if ('module' in entry) {\n bindModule(container, entry.module, entry.options);\n } else {\n bindModule(container, entry);\n }\n });\n}\n\n/**\n * Declares a module binding\n *\n * @param declaration - a module declaration\n * @param declaration.token - optional field\n *\n * @example\n * ```ts\n * const LOGGER_MODULE = declareModule<LoggerModule>({\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * });\n * ```\n */\nexport function declareModule<T extends Module<AnyObject>>(\n declaration: Omit<ModuleDeclaration<T>, 'token'> &\n Partial<Pick<ModuleDeclaration<T>, 'token'>>,\n): ModuleDeclaration<T> {\n return { ...declaration, token: declaration.token ?? token() };\n}\n\n/**\n * @deprecated Use `declareModule` instead\n *\n * Declares bindings of several modules\n *\n * @param modules - module declaration entries\n */\nexport function declareModuleBindings(\n modules: ReadonlyArray<ModuleBindingEntry>,\n): ModuleDeclaration<Module> {\n return declareModule({\n factory: () => ({}),\n imports: modules,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/tokens.ts","../src/container.ts","../src/utils.ts","../src/modules.ts"],"sourcesContent":["/**\n * @ignore\n * Binding token for mandatory value\n */\nexport type RequiredToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional?: false;\n};\n\n/**\n * @ignore\n * Binding token for optional value\n */\nexport type OptionalToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional: true;\n optionalValue: T;\n};\n\n/**\n * Binding token\n */\nexport type Token<T> = RequiredToken<T> | OptionalToken<T>;\n\n/**\n * Token options\n */\nexport type TokenOptions =\n | {\n /**\n * Key for token's symbol. It allows to create shareable tokens.\n */\n key: string;\n\n /** @ignore */\n description?: undefined;\n }\n | {\n /** Description for better error messages */\n description?: string;\n\n /** @ignore */\n key?: undefined;\n };\n\n/**\n * Creates a new binding token.\n * @param description - Token description for better error messages.\n */\nexport function token<T>(description?: string): Token<T>;\n/**\n * Creates a new binding token.\n * @param options - Token description for better error messages.\n */ export function token<T>(options?: TokenOptions): Token<T>;\nexport function token<T>(options?: TokenOptions | string): Token<T> {\n const normalized: TokenOptions | undefined =\n typeof options === 'string' ? { description: options } : options;\n\n const symbol: symbol = normalized?.key\n ? Symbol.for(normalized.key)\n : Symbol(normalized?.description);\n\n return { symbol };\n}\n\n/**\n * Decorate a token with an optional value.\n * This value is be used as default value in case a container does not have registered token.\n * @param token - Existed token.\n * @param optionalValue - Default value for the resolver.\n */\nexport function optional<T>(\n token: Token<T>,\n optionalValue: T,\n): OptionalToken<T>;\nexport function optional<T>(token: Token<T>): OptionalToken<T | undefined>;\nexport function optional<T>(\n token: Token<T>,\n optionalValue?: T,\n): OptionalToken<T | undefined> {\n return {\n symbol: token.symbol,\n isOptional: true,\n optionalValue,\n };\n}\n","import { Token, token } from './tokens';\n\n/**\n * ResolverError is thrown by the resolver when a token is not found in a container.\n */\nexport class ResolverError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = 'ResolverError';\n }\n}\n\n/**\n * @see https://github.com/mnasyrov/ditox#factory-lifetimes\n */\nexport type FactoryScope = 'scoped' | 'singleton' | 'transient';\n\n/**\n * Options for factory binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n * - `transient` - The value is created every time it is resolved.\n *\n * `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.\n */\nexport type FactoryOptions<T> =\n | {\n scope?: 'scoped' | 'singleton';\n onRemoved?: (value: T) => void;\n }\n | {\n scope: 'transient';\n };\n\n/**\n * Dependency container.\n */\nexport type Container = {\n /**\n * Binds a value for the token\n */\n bindValue<T>(token: Token<T>, value: T): void;\n\n /**\n * Binds a factory for the token.\n */\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void;\n\n /**\n * Checks if the token is registered in the container hierarchy.\n */\n hasToken(token: Token<unknown>): boolean;\n\n /**\n * Returns a resolved value by the token, or returns `undefined` in case the token is not found.\n */\n get<T>(token: Token<T>): T | undefined;\n\n /**\n * Returns a resolved value by the token or throws `ResolverError` in case the token is not found.\n */\n resolve<T>(token: Token<T>): T;\n\n /**\n * Removes a binding for the token.\n */\n remove<T>(token: Token<T>): void;\n\n /**\n * Removes all bindings in the container.\n * Continues after cleanup failures and rethrows the first thrown value unchanged\n * after clearing bindings and restoring internal tokens. Later failures are discarded.\n */\n removeAll(): void;\n};\n\n/**\n * A subset of Container interface that provides read-only access to dependency resolution.\n * This type is used for parent containers to allow token resolution without exposing mutation methods.\n */\nexport type ContainerResolver = Pick<Container, 'hasToken' | 'get' | 'resolve'>;\n\n/** @internal */\nexport const CONTAINER: Token<Container> = token('ditox.Container');\n/** @internal */\nexport const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(\n 'ditox.ParentContainer',\n);\n/** @internal */\nexport const RESOLVER: Token<Resolver> = token('ditox.Resolver');\n\n/** @internal */\nconst NOT_FOUND = Symbol();\n\n/** @internal */\nconst DEFAULT_SCOPE: FactoryScope = 'singleton';\n\n/** @internal */\ntype FactoryContext<T> = {\n factory: (container: Container) => T;\n options?: FactoryOptions<T>;\n};\n\n/** @internal */\ntype ValuesMap = Map<symbol, any>;\n\n/** @internal */\nexport type FactoriesMap = Map<symbol, FactoryContext<any>>;\n\n/** @internal */\nexport const FACTORIES_MAP: Token<FactoriesMap> = token('ditox.FactoriesMap');\n\n/** @internal */\ntype Resolver = <T>(token: Token<T>, origin: Container) => T | typeof NOT_FOUND;\n\n/** @internal */\nfunction getScope<T>(options?: FactoryOptions<T>): FactoryScope {\n return options?.scope ?? DEFAULT_SCOPE;\n}\n\n/** @internal */\nfunction getOnRemoved<T>(options: FactoryOptions<T>) {\n return options.scope === undefined ||\n options.scope === 'scoped' ||\n options.scope === 'singleton'\n ? options.onRemoved\n : undefined;\n}\n\n/** @internal */\nfunction isInternalToken<T>(token: Token<T>): boolean {\n return (\n token.symbol === CONTAINER.symbol ||\n token.symbol === PARENT_CONTAINERS.symbol ||\n token.symbol === RESOLVER.symbol\n );\n}\n\n/**\n * Creates a new dependency container.\n *\n * Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.\n *\n * @param parentArg - Optional parent container or an array of containers.\n */\nexport function createContainer(\n parentArg?: ContainerResolver | ReadonlyArray<ContainerResolver>,\n): Container {\n const parents: ReadonlyArray<ContainerResolver> | undefined = parentArg\n ? Array.isArray(parentArg)\n ? [...parentArg]\n : [parentArg]\n : undefined;\n\n const values: ValuesMap = new Map<symbol, any>();\n const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();\n\n const container: Container = {\n bindValue<T>(token: Token<T>, value: T): void {\n if (isInternalToken(token)) {\n return;\n }\n\n values.set(token.symbol, value);\n },\n\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void {\n if (isInternalToken(token)) {\n return;\n }\n\n factories.set(token.symbol, { factory, options });\n },\n\n remove<T>(token: Token<T>): void {\n if (isInternalToken(token)) {\n return;\n }\n\n const options = factories.get(token.symbol)?.options;\n if (options) {\n executeOnRemoved(token.symbol, options);\n }\n\n values.delete(token.symbol);\n factories.delete(token.symbol);\n },\n\n removeAll(): void {\n // A separate flag preserves thrown values such as undefined or false.\n let hasError = false;\n let firstError: unknown;\n try {\n factories.forEach((context, tokenSymbol) => {\n try {\n if (context.options) {\n executeOnRemoved(tokenSymbol, context.options);\n }\n } catch (error) {\n if (!hasError) {\n hasError = true;\n firstError = error;\n }\n }\n });\n } finally {\n values.clear();\n factories.clear();\n bindInternalTokens();\n }\n if (hasError) throw firstError;\n },\n\n hasToken(token: Token<unknown>): boolean {\n return (\n values.has(token.symbol) ||\n factories.has(token.symbol) ||\n parentContainersHaveToken(parents, token)\n );\n },\n\n get<T>(token: Token<T>): T | undefined {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n return undefined;\n },\n\n resolve<T>(token: Token<T>): T {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n throw new ResolverError(\n `Token \"${token.symbol.description ?? ''}\" is not provided`,\n );\n },\n };\n\n function resolver<T>(\n token: Token<T>,\n origin: Container,\n ): T | typeof NOT_FOUND {\n const value = values.get(token.symbol);\n const hasValue = value !== undefined || values.has(token.symbol);\n\n if (hasValue && origin === container) {\n return value;\n }\n\n const factoryContext = factories.get(token.symbol);\n if (factoryContext) {\n const scope = getScope(factoryContext.options);\n\n switch (scope) {\n case 'singleton': {\n if (hasValue) {\n return value;\n } else if (parentContainersHaveToken(parents, token)) {\n break;\n } else {\n // Cache the value in the same container where the factory is registered.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'scoped': {\n if (hasValue) {\n return value;\n } else {\n // Create a value within the factory's container and cache it.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'transient': {\n // Create a value within the origin container and don't cache it.\n return factoryContext.factory(origin);\n }\n }\n }\n\n if (hasValue) {\n return value;\n }\n\n if (parents) {\n return parentContainersResolveToken(parents, token, origin);\n }\n\n return NOT_FOUND;\n }\n\n function executeOnRemoved<T>(\n tokenSymbol: symbol,\n options: FactoryOptions<T>,\n ) {\n const onRemoved = getOnRemoved(options);\n if (onRemoved) {\n const value = values.get(tokenSymbol);\n if (value !== undefined || values.has(tokenSymbol)) {\n onRemoved(value);\n }\n }\n }\n\n function bindInternalTokens() {\n values.set(CONTAINER.symbol, container);\n values.set(RESOLVER.symbol, resolver);\n values.set(FACTORIES_MAP.symbol, factories);\n\n if (parents) {\n values.set(PARENT_CONTAINERS.symbol, parents);\n }\n }\n\n bindInternalTokens();\n return container;\n}\n\nfunction parentContainersHaveToken<T>(\n parents: ReadonlyArray<ContainerResolver> | undefined,\n token: Token<T>,\n): boolean {\n if (!parents) return false;\n\n for (let i = 0; i < parents.length; i++) {\n if (parents[i].hasToken(token)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parentContainersResolveToken<T>(\n parents: ReadonlyArray<ContainerResolver>,\n token: Token<T>,\n origin: Container,\n): T | typeof NOT_FOUND {\n for (const parentContainer of parents) {\n const parentResolver = parentContainer.get(RESOLVER);\n if (parentResolver) {\n const resolved = parentResolver(token, origin);\n if (resolved !== NOT_FOUND) {\n return resolved;\n }\n }\n }\n\n return NOT_FOUND;\n}\n","import type { Container } from './container';\nimport { Token } from './tokens';\n\ntype ValuesProps = { [key: string]: unknown };\ntype TokenProps<Props extends ValuesProps> = {\n [K in keyof Props]: Token<Props[K]>;\n};\n\n/**\n * Checks if a value is the token\n */\nexport function isToken<T>(value: unknown): value is Token<T> {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n 'symbol' in value &&\n typeof (value as any).symbol === 'symbol'\n );\n}\n\n/**\n * Rebinds the array by the token with added new value.\n * @param container - Dependency container.\n * @param token - Token for an array of values.\n * @param value - New value which is added to the end of the array.\n */\nexport function bindMultiValue<T>(\n container: Container,\n token: Token<ReadonlyArray<T>>,\n value: T,\n): void {\n const prevValues = container.get(token) ?? [];\n const nextValues = [...prevValues, value];\n container.bindValue(token, nextValues);\n}\n\n/**\n * Tries to resolve a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const value = tryResolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = tryResolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function tryResolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V | undefined\n : Tokens extends TokenProps<infer Props>\n ? Partial<Props>\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.get(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach((key) => (obj[key] = container.get(token[key])));\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const items1 = tryResolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = tryResolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function tryResolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V | undefined\n : Tokens[K] extends TokenProps<infer Props>\n ? Partial<Props>\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => tryResolveValue(container, item)) as Values;\n}\n\n/**\n * Resolves a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a value is not found by the token, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const value = resolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = resolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function resolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V\n : Tokens extends TokenProps<infer Props>\n ? Props\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.resolve(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach(\n (key) => (obj[key] = container.resolve(token[key])),\n );\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const items1 = resolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = resolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function resolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => resolveValue(container, item)) as Values;\n}\n\n/**\n * Decorates a factory by passing resolved values as factory arguments.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param factory - A factory.\n * @param tokens - Tokens which correspond to factory arguments.\n *\n * @return Decorated factory which takes a dependency container as a single argument.\n */\nexport function injectable<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n factory: (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return (container) => {\n const values: Values = resolveValues(container, ...tokens);\n return factory.apply(this, values);\n };\n}\n\n/**\n * Decorates a class by passing resolved values as arguments to its constructor.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param constructor - Constructor of a class\n * @param tokens - Tokens which correspond to constructor arguments\n *\n * @return A factory function which takes a dependency container as a single argument\n * and returns a new created class.\n */\nexport function injectableClass<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n constructor: new (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return injectable<Tokens, Values, Result>(\n (...values) => new constructor(...values),\n ...tokens,\n );\n}\n","import { Container } from './container';\nimport { Token, token } from './tokens';\nimport { injectable } from './utils';\n\ntype AnyObject = Record<string, any>;\ntype EmptyObject = Record<string, never>;\n\ntype ModuleController = {\n /** Dispose the module and clean its resources */\n destroy?: () => void;\n};\n\n/**\n * Dependency module\n *\n * @example\n * ```ts\n * type LoggerModule = Module<{\n * logger: Logger;\n * }>;\n * ```\n */\nexport type Module<ModuleProps extends AnyObject = EmptyObject> =\n ModuleController & ModuleProps;\n\ntype GetModuleProps<T> = T extends Module<infer Props> ? Props : never;\n\n/**\n * Description how to bind the module in declarative way.\n *\n * @example\n * ```ts\n * const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {\n * token: LOGGER_MODULE_TOKEN,\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * };\n * ```\n */\nexport type ModuleDeclaration<T extends Module<AnyObject>> = {\n /** Token for the module */\n token: Token<T>;\n\n /** Modules for binding */\n imports?: ReadonlyArray<ModuleBindingEntry>;\n\n /** Factory of the module */\n factory: (container: Container) => T;\n\n /** Dictionary of module properties which are bound to tokens. */\n exports?: {\n [K in keyof GetModuleProps<T>]?: Token<GetModuleProps<T>[K]>;\n };\n\n /** Callback could be used to prepare an environment. It is called before binding the module. */\n beforeBinding?: (container: Container) => void;\n\n /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */\n afterBinding?: (container: Container) => void;\n\n /**\n * Strategy for executing the factory:\n * - `lazy` - **This is the default**. The factory is called when the module is resolved.\n * - `eager` - The factory is called immediately after the module is bound to the container.\n */\n strategy?: 'eager' | 'lazy';\n};\n\nexport type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;\n\n/**\n * Options for module binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n */\nexport type BindModuleOptions = {\n scope?: 'scoped' | 'singleton';\n};\n\ntype ModuleDeclarationWithOptions = {\n module: ModuleDeclaration<AnyObject>;\n options: BindModuleOptions;\n};\n\nexport type ModuleBindingEntry =\n | ModuleDeclaration<AnyObject>\n | ModuleDeclarationWithOptions;\n\n/**\n * Binds the dependency module to the container\n * @param container - Dependency container.\n * @param moduleDeclaration - Declaration of the dependency module.\n * @param options - Options for module binding.\n *\n * @example\n * ```ts\n * bindModule(container, LOGGER_MODULE);\n * ```\n */\nexport function bindModule<T extends Module<AnyObject>>(\n container: Container,\n moduleDeclaration: ModuleDeclaration<T>,\n options?: BindModuleOptions,\n): void {\n const rootEntry: ModuleBindingEntry = {\n module: moduleDeclaration,\n options: options ?? {},\n };\n\n const bfsVisits = new Set<ModuleBindingEntry>([rootEntry]);\n const bfsQueue: ModuleBindingEntry[] = [rootEntry];\n\n let bfsIndex = 0;\n while (bfsIndex < bfsQueue.length) {\n const entry = bfsQueue[bfsIndex];\n\n const m = 'module' in entry ? entry.module : entry;\n\n m.imports?.forEach((depEntry) => {\n if (!bfsVisits.has(depEntry)) {\n bfsVisits.add(depEntry);\n bfsQueue.push(depEntry);\n }\n });\n\n bfsIndex++;\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.beforeBinding?.(container);\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n bindModuleEntry(container, entry);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.afterBinding?.(container);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n if (m.strategy === 'eager') {\n container.resolve(m.token);\n }\n }\n}\n\nfunction bindModuleEntry(\n container: Container,\n entry: ModuleBindingEntry,\n): void {\n let module: ModuleDeclaration<AnyObject>;\n let options: BindModuleOptions | undefined;\n\n if ('module' in entry) {\n module = entry.module;\n options = entry.options;\n } else {\n module = entry;\n }\n\n const scope = options?.scope;\n const exportedValueTokens = new Set<Token<unknown>>();\n const moduleExports = module.exports;\n\n if (moduleExports) {\n const keys = Object.keys(moduleExports);\n\n keys.forEach((valueKey) => {\n const valueToken = moduleExports[valueKey];\n if (valueToken) {\n exportedValueTokens.add(valueToken);\n\n container.bindFactory(\n valueToken,\n injectable((module) => module[valueKey], module.token),\n { scope },\n );\n }\n });\n }\n\n container.bindFactory(module.token, module.factory, {\n scope,\n onRemoved: (moduleInstance) => {\n if (moduleInstance.destroy) {\n moduleInstance.destroy();\n }\n\n exportedValueTokens.forEach((valueToken) => container.remove(valueToken));\n exportedValueTokens.clear();\n },\n });\n}\n\n/**\n * Binds dependency modules to the container\n *\n * @param container - Dependency container for binding\n * @param modules - Array of module binding entries: module declaration or `{module: ModuleDeclaration, options: BindModuleOptions}` objects.\n */\nexport function bindModules(\n container: Container,\n modules: ReadonlyArray<ModuleBindingEntry>,\n): void {\n modules.forEach((entry) => {\n if ('module' in entry) {\n bindModule(container, entry.module, entry.options);\n } else {\n bindModule(container, entry);\n }\n });\n}\n\n/**\n * Declares a module binding\n *\n * @param declaration - a module declaration\n * @param declaration.token - optional field\n *\n * @example\n * ```ts\n * const LOGGER_MODULE = declareModule<LoggerModule>({\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * });\n * ```\n */\nexport function declareModule<T extends Module<AnyObject>>(\n declaration: Omit<ModuleDeclaration<T>, 'token'> &\n Partial<Pick<ModuleDeclaration<T>, 'token'>>,\n): ModuleDeclaration<T> {\n return { ...declaration, token: declaration.token ?? token() };\n}\n\n/**\n * @deprecated Use `declareModule` instead\n *\n * Declares bindings of several modules\n *\n * @param modules - module declaration entries\n */\nexport function declareModuleBindings(\n modules: ReadonlyArray<ModuleBindingEntry>,\n): ModuleDeclaration<Module> {\n return declareModule({\n factory: () => ({}),\n imports: modules,\n });\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -111,6 +111,8 @@ type Container = {
|
|
|
111
111
|
remove<T>(token: Token<T>): void;
|
|
112
112
|
/**
|
|
113
113
|
* Removes all bindings in the container.
|
|
114
|
+
* Continues after cleanup failures and rethrows the first thrown value unchanged
|
|
115
|
+
* after clearing bindings and restoring internal tokens. Later failures are discarded.
|
|
114
116
|
*/
|
|
115
117
|
removeAll(): void;
|
|
116
118
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -111,6 +111,8 @@ type Container = {
|
|
|
111
111
|
remove<T>(token: Token<T>): void;
|
|
112
112
|
/**
|
|
113
113
|
* Removes all bindings in the container.
|
|
114
|
+
* Continues after cleanup failures and rethrows the first thrown value unchanged
|
|
115
|
+
* after clearing bindings and restoring internal tokens. Later failures are discarded.
|
|
114
116
|
*/
|
|
115
117
|
removeAll(): void;
|
|
116
118
|
};
|
package/dist/index.js
CHANGED
|
@@ -80,12 +80,25 @@ function createContainer(parentArg) {
|
|
|
80
80
|
factories.delete(token.symbol);
|
|
81
81
|
},
|
|
82
82
|
removeAll() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
let hasError = false;
|
|
84
|
+
let firstError;
|
|
85
|
+
try {
|
|
86
|
+
factories.forEach((context, tokenSymbol) => {
|
|
87
|
+
try {
|
|
88
|
+
if (context.options) executeOnRemoved(tokenSymbol, context.options);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!hasError) {
|
|
91
|
+
hasError = true;
|
|
92
|
+
firstError = error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
} finally {
|
|
97
|
+
values.clear();
|
|
98
|
+
factories.clear();
|
|
99
|
+
bindInternalTokens();
|
|
100
|
+
}
|
|
101
|
+
if (hasError) throw firstError;
|
|
89
102
|
},
|
|
90
103
|
hasToken(token) {
|
|
91
104
|
return values.has(token.symbol) || factories.has(token.symbol) || parentContainersHaveToken(parents, token);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/tokens.ts","../src/container.ts","../src/utils.ts","../src/modules.ts"],"sourcesContent":["/**\n * @ignore\n * Binding token for mandatory value\n */\nexport type RequiredToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional?: false;\n};\n\n/**\n * @ignore\n * Binding token for optional value\n */\nexport type OptionalToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional: true;\n optionalValue: T;\n};\n\n/**\n * Binding token\n */\nexport type Token<T> = RequiredToken<T> | OptionalToken<T>;\n\n/**\n * Token options\n */\nexport type TokenOptions =\n | {\n /**\n * Key for token's symbol. It allows to create shareable tokens.\n */\n key: string;\n\n /** @ignore */\n description?: undefined;\n }\n | {\n /** Description for better error messages */\n description?: string;\n\n /** @ignore */\n key?: undefined;\n };\n\n/**\n * Creates a new binding token.\n * @param description - Token description for better error messages.\n */\nexport function token<T>(description?: string): Token<T>;\n/**\n * Creates a new binding token.\n * @param options - Token description for better error messages.\n */ export function token<T>(options?: TokenOptions): Token<T>;\nexport function token<T>(options?: TokenOptions | string): Token<T> {\n const normalized: TokenOptions | undefined =\n typeof options === 'string' ? { description: options } : options;\n\n const symbol: symbol = normalized?.key\n ? Symbol.for(normalized.key)\n : Symbol(normalized?.description);\n\n return { symbol };\n}\n\n/**\n * Decorate a token with an optional value.\n * This value is be used as default value in case a container does not have registered token.\n * @param token - Existed token.\n * @param optionalValue - Default value for the resolver.\n */\nexport function optional<T>(\n token: Token<T>,\n optionalValue: T,\n): OptionalToken<T>;\nexport function optional<T>(token: Token<T>): OptionalToken<T | undefined>;\nexport function optional<T>(\n token: Token<T>,\n optionalValue?: T,\n): OptionalToken<T | undefined> {\n return {\n symbol: token.symbol,\n isOptional: true,\n optionalValue,\n };\n}\n","import { Token, token } from './tokens';\n\n/**\n * ResolverError is thrown by the resolver when a token is not found in a container.\n */\nexport class ResolverError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = 'ResolverError';\n }\n}\n\n/**\n * @see https://github.com/mnasyrov/ditox#factory-lifetimes\n */\nexport type FactoryScope = 'scoped' | 'singleton' | 'transient';\n\n/**\n * Options for factory binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n * - `transient` - The value is created every time it is resolved.\n *\n * `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.\n */\nexport type FactoryOptions<T> =\n | {\n scope?: 'scoped' | 'singleton';\n onRemoved?: (value: T) => void;\n }\n | {\n scope: 'transient';\n };\n\n/**\n * Dependency container.\n */\nexport type Container = {\n /**\n * Binds a value for the token\n */\n bindValue<T>(token: Token<T>, value: T): void;\n\n /**\n * Binds a factory for the token.\n */\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void;\n\n /**\n * Checks if the token is registered in the container hierarchy.\n */\n hasToken(token: Token<unknown>): boolean;\n\n /**\n * Returns a resolved value by the token, or returns `undefined` in case the token is not found.\n */\n get<T>(token: Token<T>): T | undefined;\n\n /**\n * Returns a resolved value by the token or throws `ResolverError` in case the token is not found.\n */\n resolve<T>(token: Token<T>): T;\n\n /**\n * Removes a binding for the token.\n */\n remove<T>(token: Token<T>): void;\n\n /**\n * Removes all bindings in the container.\n */\n removeAll(): void;\n};\n\n/**\n * A subset of Container interface that provides read-only access to dependency resolution.\n * This type is used for parent containers to allow token resolution without exposing mutation methods.\n */\nexport type ContainerResolver = Pick<Container, 'hasToken' | 'get' | 'resolve'>;\n\n/** @internal */\nexport const CONTAINER: Token<Container> = token('ditox.Container');\n/** @internal */\nexport const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(\n 'ditox.ParentContainer',\n);\n/** @internal */\nexport const RESOLVER: Token<Resolver> = token('ditox.Resolver');\n\n/** @internal */\nconst NOT_FOUND = Symbol();\n\n/** @internal */\nconst DEFAULT_SCOPE: FactoryScope = 'singleton';\n\n/** @internal */\ntype FactoryContext<T> = {\n factory: (container: Container) => T;\n options?: FactoryOptions<T>;\n};\n\n/** @internal */\ntype ValuesMap = Map<symbol, any>;\n\n/** @internal */\nexport type FactoriesMap = Map<symbol, FactoryContext<any>>;\n\n/** @internal */\nexport const FACTORIES_MAP: Token<FactoriesMap> = token('ditox.FactoriesMap');\n\n/** @internal */\ntype Resolver = <T>(token: Token<T>, origin: Container) => T | typeof NOT_FOUND;\n\n/** @internal */\nfunction getScope<T>(options?: FactoryOptions<T>): FactoryScope {\n return options?.scope ?? DEFAULT_SCOPE;\n}\n\n/** @internal */\nfunction getOnRemoved<T>(options: FactoryOptions<T>) {\n return options.scope === undefined ||\n options.scope === 'scoped' ||\n options.scope === 'singleton'\n ? options.onRemoved\n : undefined;\n}\n\n/** @internal */\nfunction isInternalToken<T>(token: Token<T>): boolean {\n return (\n token.symbol === CONTAINER.symbol ||\n token.symbol === PARENT_CONTAINERS.symbol ||\n token.symbol === RESOLVER.symbol\n );\n}\n\n/**\n * Creates a new dependency container.\n *\n * Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.\n *\n * @param parentArg - Optional parent container or an array of containers.\n */\nexport function createContainer(\n parentArg?: ContainerResolver | ReadonlyArray<ContainerResolver>,\n): Container {\n const parents: ReadonlyArray<ContainerResolver> | undefined = parentArg\n ? Array.isArray(parentArg)\n ? [...parentArg]\n : [parentArg]\n : undefined;\n\n const values: ValuesMap = new Map<symbol, any>();\n const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();\n\n const container: Container = {\n bindValue<T>(token: Token<T>, value: T): void {\n if (isInternalToken(token)) {\n return;\n }\n\n values.set(token.symbol, value);\n },\n\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void {\n if (isInternalToken(token)) {\n return;\n }\n\n factories.set(token.symbol, { factory, options });\n },\n\n remove<T>(token: Token<T>): void {\n if (isInternalToken(token)) {\n return;\n }\n\n const options = factories.get(token.symbol)?.options;\n if (options) {\n executeOnRemoved(token.symbol, options);\n }\n\n values.delete(token.symbol);\n factories.delete(token.symbol);\n },\n\n removeAll(): void {\n factories.forEach((context, tokenSymbol) => {\n if (context.options) {\n executeOnRemoved(tokenSymbol, context.options);\n }\n });\n\n values.clear();\n factories.clear();\n bindInternalTokens();\n },\n\n hasToken(token: Token<unknown>): boolean {\n return (\n values.has(token.symbol) ||\n factories.has(token.symbol) ||\n parentContainersHaveToken(parents, token)\n );\n },\n\n get<T>(token: Token<T>): T | undefined {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n return undefined;\n },\n\n resolve<T>(token: Token<T>): T {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n throw new ResolverError(\n `Token \"${token.symbol.description ?? ''}\" is not provided`,\n );\n },\n };\n\n function resolver<T>(\n token: Token<T>,\n origin: Container,\n ): T | typeof NOT_FOUND {\n const value = values.get(token.symbol);\n const hasValue = value !== undefined || values.has(token.symbol);\n\n if (hasValue && origin === container) {\n return value;\n }\n\n const factoryContext = factories.get(token.symbol);\n if (factoryContext) {\n const scope = getScope(factoryContext.options);\n\n switch (scope) {\n case 'singleton': {\n if (hasValue) {\n return value;\n } else if (parentContainersHaveToken(parents, token)) {\n break;\n } else {\n // Cache the value in the same container where the factory is registered.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'scoped': {\n if (hasValue) {\n return value;\n } else {\n // Create a value within the factory's container and cache it.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'transient': {\n // Create a value within the origin container and don't cache it.\n return factoryContext.factory(origin);\n }\n }\n }\n\n if (hasValue) {\n return value;\n }\n\n if (parents) {\n return parentContainersResolveToken(parents, token, origin);\n }\n\n return NOT_FOUND;\n }\n\n function executeOnRemoved<T>(\n tokenSymbol: symbol,\n options: FactoryOptions<T>,\n ) {\n const onRemoved = getOnRemoved(options);\n if (onRemoved) {\n const value = values.get(tokenSymbol);\n if (value !== undefined || values.has(tokenSymbol)) {\n onRemoved(value);\n }\n }\n }\n\n function bindInternalTokens() {\n values.set(CONTAINER.symbol, container);\n values.set(RESOLVER.symbol, resolver);\n values.set(FACTORIES_MAP.symbol, factories);\n\n if (parents) {\n values.set(PARENT_CONTAINERS.symbol, parents);\n }\n }\n\n bindInternalTokens();\n return container;\n}\n\nfunction parentContainersHaveToken<T>(\n parents: ReadonlyArray<ContainerResolver> | undefined,\n token: Token<T>,\n): boolean {\n if (!parents) return false;\n\n for (let i = 0; i < parents.length; i++) {\n if (parents[i].hasToken(token)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parentContainersResolveToken<T>(\n parents: ReadonlyArray<ContainerResolver>,\n token: Token<T>,\n origin: Container,\n): T | typeof NOT_FOUND {\n for (const parentContainer of parents) {\n const parentResolver = parentContainer.get(RESOLVER);\n if (parentResolver) {\n const resolved = parentResolver(token, origin);\n if (resolved !== NOT_FOUND) {\n return resolved;\n }\n }\n }\n\n return NOT_FOUND;\n}\n","import type { Container } from './container';\nimport { Token } from './tokens';\n\ntype ValuesProps = { [key: string]: unknown };\ntype TokenProps<Props extends ValuesProps> = {\n [K in keyof Props]: Token<Props[K]>;\n};\n\n/**\n * Checks if a value is the token\n */\nexport function isToken<T>(value: unknown): value is Token<T> {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n 'symbol' in value &&\n typeof (value as any).symbol === 'symbol'\n );\n}\n\n/**\n * Rebinds the array by the token with added new value.\n * @param container - Dependency container.\n * @param token - Token for an array of values.\n * @param value - New value which is added to the end of the array.\n */\nexport function bindMultiValue<T>(\n container: Container,\n token: Token<ReadonlyArray<T>>,\n value: T,\n): void {\n const prevValues = container.get(token) ?? [];\n const nextValues = [...prevValues, value];\n container.bindValue(token, nextValues);\n}\n\n/**\n * Tries to resolve a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const value = tryResolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = tryResolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function tryResolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V | undefined\n : Tokens extends TokenProps<infer Props>\n ? Partial<Props>\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.get(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach((key) => (obj[key] = container.get(token[key])));\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const items1 = tryResolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = tryResolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function tryResolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V | undefined\n : Tokens[K] extends TokenProps<infer Props>\n ? Partial<Props>\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => tryResolveValue(container, item)) as Values;\n}\n\n/**\n * Resolves a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a value is not found by the token, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const value = resolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = resolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function resolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V\n : Tokens extends TokenProps<infer Props>\n ? Props\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.resolve(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach(\n (key) => (obj[key] = container.resolve(token[key])),\n );\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const items1 = resolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = resolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function resolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => resolveValue(container, item)) as Values;\n}\n\n/**\n * Decorates a factory by passing resolved values as factory arguments.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param factory - A factory.\n * @param tokens - Tokens which correspond to factory arguments.\n *\n * @return Decorated factory which takes a dependency container as a single argument.\n */\nexport function injectable<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n factory: (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return (container) => {\n const values: Values = resolveValues(container, ...tokens);\n return factory.apply(this, values);\n };\n}\n\n/**\n * Decorates a class by passing resolved values as arguments to its constructor.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param constructor - Constructor of a class\n * @param tokens - Tokens which correspond to constructor arguments\n *\n * @return A factory function which takes a dependency container as a single argument\n * and returns a new created class.\n */\nexport function injectableClass<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n constructor: new (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return injectable<Tokens, Values, Result>(\n (...values) => new constructor(...values),\n ...tokens,\n );\n}\n","import { Container } from './container';\nimport { Token, token } from './tokens';\nimport { injectable } from './utils';\n\ntype AnyObject = Record<string, any>;\ntype EmptyObject = Record<string, never>;\n\ntype ModuleController = {\n /** Dispose the module and clean its resources */\n destroy?: () => void;\n};\n\n/**\n * Dependency module\n *\n * @example\n * ```ts\n * type LoggerModule = Module<{\n * logger: Logger;\n * }>;\n * ```\n */\nexport type Module<ModuleProps extends AnyObject = EmptyObject> =\n ModuleController & ModuleProps;\n\ntype GetModuleProps<T> = T extends Module<infer Props> ? Props : never;\n\n/**\n * Description how to bind the module in declarative way.\n *\n * @example\n * ```ts\n * const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {\n * token: LOGGER_MODULE_TOKEN,\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * };\n * ```\n */\nexport type ModuleDeclaration<T extends Module<AnyObject>> = {\n /** Token for the module */\n token: Token<T>;\n\n /** Modules for binding */\n imports?: ReadonlyArray<ModuleBindingEntry>;\n\n /** Factory of the module */\n factory: (container: Container) => T;\n\n /** Dictionary of module properties which are bound to tokens. */\n exports?: {\n [K in keyof GetModuleProps<T>]?: Token<GetModuleProps<T>[K]>;\n };\n\n /** Callback could be used to prepare an environment. It is called before binding the module. */\n beforeBinding?: (container: Container) => void;\n\n /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */\n afterBinding?: (container: Container) => void;\n\n /**\n * Strategy for executing the factory:\n * - `lazy` - **This is the default**. The factory is called when the module is resolved.\n * - `eager` - The factory is called immediately after the module is bound to the container.\n */\n strategy?: 'eager' | 'lazy';\n};\n\nexport type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;\n\n/**\n * Options for module binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n */\nexport type BindModuleOptions = {\n scope?: 'scoped' | 'singleton';\n};\n\ntype ModuleDeclarationWithOptions = {\n module: ModuleDeclaration<AnyObject>;\n options: BindModuleOptions;\n};\n\nexport type ModuleBindingEntry =\n | ModuleDeclaration<AnyObject>\n | ModuleDeclarationWithOptions;\n\n/**\n * Binds the dependency module to the container\n * @param container - Dependency container.\n * @param moduleDeclaration - Declaration of the dependency module.\n * @param options - Options for module binding.\n *\n * @example\n * ```ts\n * bindModule(container, LOGGER_MODULE);\n * ```\n */\nexport function bindModule<T extends Module<AnyObject>>(\n container: Container,\n moduleDeclaration: ModuleDeclaration<T>,\n options?: BindModuleOptions,\n): void {\n const rootEntry: ModuleBindingEntry = {\n module: moduleDeclaration,\n options: options ?? {},\n };\n\n const bfsVisits = new Set<ModuleBindingEntry>([rootEntry]);\n const bfsQueue: ModuleBindingEntry[] = [rootEntry];\n\n let bfsIndex = 0;\n while (bfsIndex < bfsQueue.length) {\n const entry = bfsQueue[bfsIndex];\n\n const m = 'module' in entry ? entry.module : entry;\n\n m.imports?.forEach((depEntry) => {\n if (!bfsVisits.has(depEntry)) {\n bfsVisits.add(depEntry);\n bfsQueue.push(depEntry);\n }\n });\n\n bfsIndex++;\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.beforeBinding?.(container);\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n bindModuleEntry(container, entry);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.afterBinding?.(container);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n if (m.strategy === 'eager') {\n container.resolve(m.token);\n }\n }\n}\n\nfunction bindModuleEntry(\n container: Container,\n entry: ModuleBindingEntry,\n): void {\n let module: ModuleDeclaration<AnyObject>;\n let options: BindModuleOptions | undefined;\n\n if ('module' in entry) {\n module = entry.module;\n options = entry.options;\n } else {\n module = entry;\n }\n\n const scope = options?.scope;\n const exportedValueTokens = new Set<Token<unknown>>();\n const moduleExports = module.exports;\n\n if (moduleExports) {\n const keys = Object.keys(moduleExports);\n\n keys.forEach((valueKey) => {\n const valueToken = moduleExports[valueKey];\n if (valueToken) {\n exportedValueTokens.add(valueToken);\n\n container.bindFactory(\n valueToken,\n injectable((module) => module[valueKey], module.token),\n { scope },\n );\n }\n });\n }\n\n container.bindFactory(module.token, module.factory, {\n scope,\n onRemoved: (moduleInstance) => {\n if (moduleInstance.destroy) {\n moduleInstance.destroy();\n }\n\n exportedValueTokens.forEach((valueToken) => container.remove(valueToken));\n exportedValueTokens.clear();\n },\n });\n}\n\n/**\n * Binds dependency modules to the container\n *\n * @param container - Dependency container for binding\n * @param modules - Array of module binding entries: module declaration or `{module: ModuleDeclaration, options: BindModuleOptions}` objects.\n */\nexport function bindModules(\n container: Container,\n modules: ReadonlyArray<ModuleBindingEntry>,\n): void {\n modules.forEach((entry) => {\n if ('module' in entry) {\n bindModule(container, entry.module, entry.options);\n } else {\n bindModule(container, entry);\n }\n });\n}\n\n/**\n * Declares a module binding\n *\n * @param declaration - a module declaration\n * @param declaration.token - optional field\n *\n * @example\n * ```ts\n * const LOGGER_MODULE = declareModule<LoggerModule>({\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * });\n * ```\n */\nexport function declareModule<T extends Module<AnyObject>>(\n declaration: Omit<ModuleDeclaration<T>, 'token'> &\n Partial<Pick<ModuleDeclaration<T>, 'token'>>,\n): ModuleDeclaration<T> {\n return { ...declaration, token: declaration.token ?? token() };\n}\n\n/**\n * @deprecated Use `declareModule` instead\n *\n * Declares bindings of several modules\n *\n * @param modules - module declaration entries\n */\nexport function declareModuleBindings(\n modules: ReadonlyArray<ModuleBindingEntry>,\n): ModuleDeclaration<Module> {\n return declareModule({\n factory: () => ({}),\n imports: modules,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/tokens.ts","../src/container.ts","../src/utils.ts","../src/modules.ts"],"sourcesContent":["/**\n * @ignore\n * Binding token for mandatory value\n */\nexport type RequiredToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional?: false;\n};\n\n/**\n * @ignore\n * Binding token for optional value\n */\nexport type OptionalToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional: true;\n optionalValue: T;\n};\n\n/**\n * Binding token\n */\nexport type Token<T> = RequiredToken<T> | OptionalToken<T>;\n\n/**\n * Token options\n */\nexport type TokenOptions =\n | {\n /**\n * Key for token's symbol. It allows to create shareable tokens.\n */\n key: string;\n\n /** @ignore */\n description?: undefined;\n }\n | {\n /** Description for better error messages */\n description?: string;\n\n /** @ignore */\n key?: undefined;\n };\n\n/**\n * Creates a new binding token.\n * @param description - Token description for better error messages.\n */\nexport function token<T>(description?: string): Token<T>;\n/**\n * Creates a new binding token.\n * @param options - Token description for better error messages.\n */ export function token<T>(options?: TokenOptions): Token<T>;\nexport function token<T>(options?: TokenOptions | string): Token<T> {\n const normalized: TokenOptions | undefined =\n typeof options === 'string' ? { description: options } : options;\n\n const symbol: symbol = normalized?.key\n ? Symbol.for(normalized.key)\n : Symbol(normalized?.description);\n\n return { symbol };\n}\n\n/**\n * Decorate a token with an optional value.\n * This value is be used as default value in case a container does not have registered token.\n * @param token - Existed token.\n * @param optionalValue - Default value for the resolver.\n */\nexport function optional<T>(\n token: Token<T>,\n optionalValue: T,\n): OptionalToken<T>;\nexport function optional<T>(token: Token<T>): OptionalToken<T | undefined>;\nexport function optional<T>(\n token: Token<T>,\n optionalValue?: T,\n): OptionalToken<T | undefined> {\n return {\n symbol: token.symbol,\n isOptional: true,\n optionalValue,\n };\n}\n","import { Token, token } from './tokens';\n\n/**\n * ResolverError is thrown by the resolver when a token is not found in a container.\n */\nexport class ResolverError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = 'ResolverError';\n }\n}\n\n/**\n * @see https://github.com/mnasyrov/ditox#factory-lifetimes\n */\nexport type FactoryScope = 'scoped' | 'singleton' | 'transient';\n\n/**\n * Options for factory binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n * - `transient` - The value is created every time it is resolved.\n *\n * `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.\n */\nexport type FactoryOptions<T> =\n | {\n scope?: 'scoped' | 'singleton';\n onRemoved?: (value: T) => void;\n }\n | {\n scope: 'transient';\n };\n\n/**\n * Dependency container.\n */\nexport type Container = {\n /**\n * Binds a value for the token\n */\n bindValue<T>(token: Token<T>, value: T): void;\n\n /**\n * Binds a factory for the token.\n */\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void;\n\n /**\n * Checks if the token is registered in the container hierarchy.\n */\n hasToken(token: Token<unknown>): boolean;\n\n /**\n * Returns a resolved value by the token, or returns `undefined` in case the token is not found.\n */\n get<T>(token: Token<T>): T | undefined;\n\n /**\n * Returns a resolved value by the token or throws `ResolverError` in case the token is not found.\n */\n resolve<T>(token: Token<T>): T;\n\n /**\n * Removes a binding for the token.\n */\n remove<T>(token: Token<T>): void;\n\n /**\n * Removes all bindings in the container.\n * Continues after cleanup failures and rethrows the first thrown value unchanged\n * after clearing bindings and restoring internal tokens. Later failures are discarded.\n */\n removeAll(): void;\n};\n\n/**\n * A subset of Container interface that provides read-only access to dependency resolution.\n * This type is used for parent containers to allow token resolution without exposing mutation methods.\n */\nexport type ContainerResolver = Pick<Container, 'hasToken' | 'get' | 'resolve'>;\n\n/** @internal */\nexport const CONTAINER: Token<Container> = token('ditox.Container');\n/** @internal */\nexport const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(\n 'ditox.ParentContainer',\n);\n/** @internal */\nexport const RESOLVER: Token<Resolver> = token('ditox.Resolver');\n\n/** @internal */\nconst NOT_FOUND = Symbol();\n\n/** @internal */\nconst DEFAULT_SCOPE: FactoryScope = 'singleton';\n\n/** @internal */\ntype FactoryContext<T> = {\n factory: (container: Container) => T;\n options?: FactoryOptions<T>;\n};\n\n/** @internal */\ntype ValuesMap = Map<symbol, any>;\n\n/** @internal */\nexport type FactoriesMap = Map<symbol, FactoryContext<any>>;\n\n/** @internal */\nexport const FACTORIES_MAP: Token<FactoriesMap> = token('ditox.FactoriesMap');\n\n/** @internal */\ntype Resolver = <T>(token: Token<T>, origin: Container) => T | typeof NOT_FOUND;\n\n/** @internal */\nfunction getScope<T>(options?: FactoryOptions<T>): FactoryScope {\n return options?.scope ?? DEFAULT_SCOPE;\n}\n\n/** @internal */\nfunction getOnRemoved<T>(options: FactoryOptions<T>) {\n return options.scope === undefined ||\n options.scope === 'scoped' ||\n options.scope === 'singleton'\n ? options.onRemoved\n : undefined;\n}\n\n/** @internal */\nfunction isInternalToken<T>(token: Token<T>): boolean {\n return (\n token.symbol === CONTAINER.symbol ||\n token.symbol === PARENT_CONTAINERS.symbol ||\n token.symbol === RESOLVER.symbol\n );\n}\n\n/**\n * Creates a new dependency container.\n *\n * Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.\n *\n * @param parentArg - Optional parent container or an array of containers.\n */\nexport function createContainer(\n parentArg?: ContainerResolver | ReadonlyArray<ContainerResolver>,\n): Container {\n const parents: ReadonlyArray<ContainerResolver> | undefined = parentArg\n ? Array.isArray(parentArg)\n ? [...parentArg]\n : [parentArg]\n : undefined;\n\n const values: ValuesMap = new Map<symbol, any>();\n const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();\n\n const container: Container = {\n bindValue<T>(token: Token<T>, value: T): void {\n if (isInternalToken(token)) {\n return;\n }\n\n values.set(token.symbol, value);\n },\n\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void {\n if (isInternalToken(token)) {\n return;\n }\n\n factories.set(token.symbol, { factory, options });\n },\n\n remove<T>(token: Token<T>): void {\n if (isInternalToken(token)) {\n return;\n }\n\n const options = factories.get(token.symbol)?.options;\n if (options) {\n executeOnRemoved(token.symbol, options);\n }\n\n values.delete(token.symbol);\n factories.delete(token.symbol);\n },\n\n removeAll(): void {\n // A separate flag preserves thrown values such as undefined or false.\n let hasError = false;\n let firstError: unknown;\n try {\n factories.forEach((context, tokenSymbol) => {\n try {\n if (context.options) {\n executeOnRemoved(tokenSymbol, context.options);\n }\n } catch (error) {\n if (!hasError) {\n hasError = true;\n firstError = error;\n }\n }\n });\n } finally {\n values.clear();\n factories.clear();\n bindInternalTokens();\n }\n if (hasError) throw firstError;\n },\n\n hasToken(token: Token<unknown>): boolean {\n return (\n values.has(token.symbol) ||\n factories.has(token.symbol) ||\n parentContainersHaveToken(parents, token)\n );\n },\n\n get<T>(token: Token<T>): T | undefined {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n return undefined;\n },\n\n resolve<T>(token: Token<T>): T {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n throw new ResolverError(\n `Token \"${token.symbol.description ?? ''}\" is not provided`,\n );\n },\n };\n\n function resolver<T>(\n token: Token<T>,\n origin: Container,\n ): T | typeof NOT_FOUND {\n const value = values.get(token.symbol);\n const hasValue = value !== undefined || values.has(token.symbol);\n\n if (hasValue && origin === container) {\n return value;\n }\n\n const factoryContext = factories.get(token.symbol);\n if (factoryContext) {\n const scope = getScope(factoryContext.options);\n\n switch (scope) {\n case 'singleton': {\n if (hasValue) {\n return value;\n } else if (parentContainersHaveToken(parents, token)) {\n break;\n } else {\n // Cache the value in the same container where the factory is registered.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'scoped': {\n if (hasValue) {\n return value;\n } else {\n // Create a value within the factory's container and cache it.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'transient': {\n // Create a value within the origin container and don't cache it.\n return factoryContext.factory(origin);\n }\n }\n }\n\n if (hasValue) {\n return value;\n }\n\n if (parents) {\n return parentContainersResolveToken(parents, token, origin);\n }\n\n return NOT_FOUND;\n }\n\n function executeOnRemoved<T>(\n tokenSymbol: symbol,\n options: FactoryOptions<T>,\n ) {\n const onRemoved = getOnRemoved(options);\n if (onRemoved) {\n const value = values.get(tokenSymbol);\n if (value !== undefined || values.has(tokenSymbol)) {\n onRemoved(value);\n }\n }\n }\n\n function bindInternalTokens() {\n values.set(CONTAINER.symbol, container);\n values.set(RESOLVER.symbol, resolver);\n values.set(FACTORIES_MAP.symbol, factories);\n\n if (parents) {\n values.set(PARENT_CONTAINERS.symbol, parents);\n }\n }\n\n bindInternalTokens();\n return container;\n}\n\nfunction parentContainersHaveToken<T>(\n parents: ReadonlyArray<ContainerResolver> | undefined,\n token: Token<T>,\n): boolean {\n if (!parents) return false;\n\n for (let i = 0; i < parents.length; i++) {\n if (parents[i].hasToken(token)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parentContainersResolveToken<T>(\n parents: ReadonlyArray<ContainerResolver>,\n token: Token<T>,\n origin: Container,\n): T | typeof NOT_FOUND {\n for (const parentContainer of parents) {\n const parentResolver = parentContainer.get(RESOLVER);\n if (parentResolver) {\n const resolved = parentResolver(token, origin);\n if (resolved !== NOT_FOUND) {\n return resolved;\n }\n }\n }\n\n return NOT_FOUND;\n}\n","import type { Container } from './container';\nimport { Token } from './tokens';\n\ntype ValuesProps = { [key: string]: unknown };\ntype TokenProps<Props extends ValuesProps> = {\n [K in keyof Props]: Token<Props[K]>;\n};\n\n/**\n * Checks if a value is the token\n */\nexport function isToken<T>(value: unknown): value is Token<T> {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n 'symbol' in value &&\n typeof (value as any).symbol === 'symbol'\n );\n}\n\n/**\n * Rebinds the array by the token with added new value.\n * @param container - Dependency container.\n * @param token - Token for an array of values.\n * @param value - New value which is added to the end of the array.\n */\nexport function bindMultiValue<T>(\n container: Container,\n token: Token<ReadonlyArray<T>>,\n value: T,\n): void {\n const prevValues = container.get(token) ?? [];\n const nextValues = [...prevValues, value];\n container.bindValue(token, nextValues);\n}\n\n/**\n * Tries to resolve a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const value = tryResolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = tryResolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function tryResolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V | undefined\n : Tokens extends TokenProps<infer Props>\n ? Partial<Props>\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.get(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach((key) => (obj[key] = container.get(token[key])));\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const items1 = tryResolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = tryResolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function tryResolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V | undefined\n : Tokens[K] extends TokenProps<infer Props>\n ? Partial<Props>\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => tryResolveValue(container, item)) as Values;\n}\n\n/**\n * Resolves a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a value is not found by the token, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const value = resolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = resolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function resolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V\n : Tokens extends TokenProps<infer Props>\n ? Props\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.resolve(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach(\n (key) => (obj[key] = container.resolve(token[key])),\n );\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const items1 = resolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = resolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function resolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => resolveValue(container, item)) as Values;\n}\n\n/**\n * Decorates a factory by passing resolved values as factory arguments.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param factory - A factory.\n * @param tokens - Tokens which correspond to factory arguments.\n *\n * @return Decorated factory which takes a dependency container as a single argument.\n */\nexport function injectable<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n factory: (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return (container) => {\n const values: Values = resolveValues(container, ...tokens);\n return factory.apply(this, values);\n };\n}\n\n/**\n * Decorates a class by passing resolved values as arguments to its constructor.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param constructor - Constructor of a class\n * @param tokens - Tokens which correspond to constructor arguments\n *\n * @return A factory function which takes a dependency container as a single argument\n * and returns a new created class.\n */\nexport function injectableClass<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n constructor: new (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return injectable<Tokens, Values, Result>(\n (...values) => new constructor(...values),\n ...tokens,\n );\n}\n","import { Container } from './container';\nimport { Token, token } from './tokens';\nimport { injectable } from './utils';\n\ntype AnyObject = Record<string, any>;\ntype EmptyObject = Record<string, never>;\n\ntype ModuleController = {\n /** Dispose the module and clean its resources */\n destroy?: () => void;\n};\n\n/**\n * Dependency module\n *\n * @example\n * ```ts\n * type LoggerModule = Module<{\n * logger: Logger;\n * }>;\n * ```\n */\nexport type Module<ModuleProps extends AnyObject = EmptyObject> =\n ModuleController & ModuleProps;\n\ntype GetModuleProps<T> = T extends Module<infer Props> ? Props : never;\n\n/**\n * Description how to bind the module in declarative way.\n *\n * @example\n * ```ts\n * const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {\n * token: LOGGER_MODULE_TOKEN,\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * };\n * ```\n */\nexport type ModuleDeclaration<T extends Module<AnyObject>> = {\n /** Token for the module */\n token: Token<T>;\n\n /** Modules for binding */\n imports?: ReadonlyArray<ModuleBindingEntry>;\n\n /** Factory of the module */\n factory: (container: Container) => T;\n\n /** Dictionary of module properties which are bound to tokens. */\n exports?: {\n [K in keyof GetModuleProps<T>]?: Token<GetModuleProps<T>[K]>;\n };\n\n /** Callback could be used to prepare an environment. It is called before binding the module. */\n beforeBinding?: (container: Container) => void;\n\n /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */\n afterBinding?: (container: Container) => void;\n\n /**\n * Strategy for executing the factory:\n * - `lazy` - **This is the default**. The factory is called when the module is resolved.\n * - `eager` - The factory is called immediately after the module is bound to the container.\n */\n strategy?: 'eager' | 'lazy';\n};\n\nexport type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;\n\n/**\n * Options for module binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n */\nexport type BindModuleOptions = {\n scope?: 'scoped' | 'singleton';\n};\n\ntype ModuleDeclarationWithOptions = {\n module: ModuleDeclaration<AnyObject>;\n options: BindModuleOptions;\n};\n\nexport type ModuleBindingEntry =\n | ModuleDeclaration<AnyObject>\n | ModuleDeclarationWithOptions;\n\n/**\n * Binds the dependency module to the container\n * @param container - Dependency container.\n * @param moduleDeclaration - Declaration of the dependency module.\n * @param options - Options for module binding.\n *\n * @example\n * ```ts\n * bindModule(container, LOGGER_MODULE);\n * ```\n */\nexport function bindModule<T extends Module<AnyObject>>(\n container: Container,\n moduleDeclaration: ModuleDeclaration<T>,\n options?: BindModuleOptions,\n): void {\n const rootEntry: ModuleBindingEntry = {\n module: moduleDeclaration,\n options: options ?? {},\n };\n\n const bfsVisits = new Set<ModuleBindingEntry>([rootEntry]);\n const bfsQueue: ModuleBindingEntry[] = [rootEntry];\n\n let bfsIndex = 0;\n while (bfsIndex < bfsQueue.length) {\n const entry = bfsQueue[bfsIndex];\n\n const m = 'module' in entry ? entry.module : entry;\n\n m.imports?.forEach((depEntry) => {\n if (!bfsVisits.has(depEntry)) {\n bfsVisits.add(depEntry);\n bfsQueue.push(depEntry);\n }\n });\n\n bfsIndex++;\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.beforeBinding?.(container);\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n bindModuleEntry(container, entry);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.afterBinding?.(container);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n if (m.strategy === 'eager') {\n container.resolve(m.token);\n }\n }\n}\n\nfunction bindModuleEntry(\n container: Container,\n entry: ModuleBindingEntry,\n): void {\n let module: ModuleDeclaration<AnyObject>;\n let options: BindModuleOptions | undefined;\n\n if ('module' in entry) {\n module = entry.module;\n options = entry.options;\n } else {\n module = entry;\n }\n\n const scope = options?.scope;\n const exportedValueTokens = new Set<Token<unknown>>();\n const moduleExports = module.exports;\n\n if (moduleExports) {\n const keys = Object.keys(moduleExports);\n\n keys.forEach((valueKey) => {\n const valueToken = moduleExports[valueKey];\n if (valueToken) {\n exportedValueTokens.add(valueToken);\n\n container.bindFactory(\n valueToken,\n injectable((module) => module[valueKey], module.token),\n { scope },\n );\n }\n });\n }\n\n container.bindFactory(module.token, module.factory, {\n scope,\n onRemoved: (moduleInstance) => {\n if (moduleInstance.destroy) {\n moduleInstance.destroy();\n }\n\n exportedValueTokens.forEach((valueToken) => container.remove(valueToken));\n exportedValueTokens.clear();\n },\n });\n}\n\n/**\n * Binds dependency modules to the container\n *\n * @param container - Dependency container for binding\n * @param modules - Array of module binding entries: module declaration or `{module: ModuleDeclaration, options: BindModuleOptions}` objects.\n */\nexport function bindModules(\n container: Container,\n modules: ReadonlyArray<ModuleBindingEntry>,\n): void {\n modules.forEach((entry) => {\n if ('module' in entry) {\n bindModule(container, entry.module, entry.options);\n } else {\n bindModule(container, entry);\n }\n });\n}\n\n/**\n * Declares a module binding\n *\n * @param declaration - a module declaration\n * @param declaration.token - optional field\n *\n * @example\n * ```ts\n * const LOGGER_MODULE = declareModule<LoggerModule>({\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * });\n * ```\n */\nexport function declareModule<T extends Module<AnyObject>>(\n declaration: Omit<ModuleDeclaration<T>, 'token'> &\n Partial<Pick<ModuleDeclaration<T>, 'token'>>,\n): ModuleDeclaration<T> {\n return { ...declaration, token: declaration.token ?? token() };\n}\n\n/**\n * @deprecated Use `declareModule` instead\n *\n * Declares bindings of several modules\n *\n * @param modules - module declaration entries\n */\nexport function declareModuleBindings(\n modules: ReadonlyArray<ModuleBindingEntry>,\n): ModuleDeclaration<Module> {\n return declareModule({\n factory: () => ({}),\n imports: modules,\n });\n}\n"],"mappings":";AAwDA,SAAgB,MAAS,SAA2C;CAClE,MAAM,aACJ,OAAO,YAAY,WAAW,EAAE,aAAa,SAAS,GAAG;AAM3D,QAAO,EAAE,iEAJc,WAAY,OAC/B,OAAO,IAAI,WAAW,IAAI,GAC1B,+DAAO,WAAY,YAAY,EAElB;;AAcnB,SAAgB,SACd,OACA,eAC8B;AAC9B,QAAO;EACL,QAAQ,MAAM;EACd,YAAY;EACZ;EACD;;;;;;;;ACjFH,IAAa,gBAAb,cAAmC,MAAM;CACvC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,SAAO,eAAe,MAAM,IAAI,OAAO,UAAU;AACjD,OAAK,OAAO;;;;AAiFhB,MAAa,YAA8B,MAAM,kBAAkB;;AAEnE,MAAa,oBAAqD,MAChE,wBACD;;AAED,MAAa,WAA4B,MAAM,iBAAiB;;AAGhE,MAAM,YAAY,QAAQ;;AAG1B,MAAM,gBAA8B;;AAepC,MAAa,gBAAqC,MAAM,qBAAqB;;AAM7E,SAAS,SAAY,SAA2C;;AAC9D,4EAAO,QAAS,gEAAS;;;AAI3B,SAAS,aAAgB,SAA4B;AACnD,QAAO,QAAQ,UAAU,UACvB,QAAQ,UAAU,YAClB,QAAQ,UAAU,cAChB,QAAQ,YACR;;;AAIN,SAAS,gBAAmB,OAA0B;AACpD,QACE,MAAM,WAAW,UAAU,UAC3B,MAAM,WAAW,kBAAkB,UACnC,MAAM,WAAW,SAAS;;;;;;;;;AAW9B,SAAgB,gBACd,WACW;CACX,MAAM,UAAwD,YAC1D,MAAM,QAAQ,UAAU,GACtB,CAAC,GAAG,UAAU,GACd,CAAC,UAAU,GACb;CAEJ,MAAM,yBAAoB,IAAI,KAAkB;CAChD,MAAM,4BAA0B,IAAI,KAAkC;CAEtE,MAAM,YAAuB;EAC3B,UAAa,OAAiB,OAAgB;AAC5C,OAAI,gBAAgB,MAAM,CACxB;AAGF,UAAO,IAAI,MAAM,QAAQ,MAAM;;EAGjC,YACE,OACA,SACA,SACM;AACN,OAAI,gBAAgB,MAAM,CACxB;AAGF,aAAU,IAAI,MAAM,QAAQ;IAAE;IAAS;IAAS,CAAC;;EAGnD,OAAU,OAAuB;;AAC/B,OAAI,gBAAgB,MAAM,CACxB;GAGF,MAAM,4BAAU,UAAU,IAAI,MAAM,OAAO,kEAAE;AAC7C,OAAI,QACF,kBAAiB,MAAM,QAAQ,QAAQ;AAGzC,UAAO,OAAO,MAAM,OAAO;AAC3B,aAAU,OAAO,MAAM,OAAO;;EAGhC,YAAkB;GAEhB,IAAI,WAAW;GACf,IAAI;AACJ,OAAI;AACF,cAAU,SAAS,SAAS,gBAAgB;AAC1C,SAAI;AACF,UAAI,QAAQ,QACV,kBAAiB,aAAa,QAAQ,QAAQ;cAEzC,OAAO;AACd,UAAI,CAAC,UAAU;AACb,kBAAW;AACX,oBAAa;;;MAGjB;aACM;AACR,WAAO,OAAO;AACd,cAAU,OAAO;AACjB,wBAAoB;;AAEtB,OAAI,SAAU,OAAM;;EAGtB,SAAS,OAAgC;AACvC,UACE,OAAO,IAAI,MAAM,OAAO,IACxB,UAAU,IAAI,MAAM,OAAO,IAC3B,0BAA0B,SAAS,MAAM;;EAI7C,IAAO,OAAgC;GACrC,MAAM,QAAQ,SAAS,OAAO,UAAU;AACxC,OAAI,UAAU,UACZ,QAAO;AAGT,OAAI,MAAM,WACR,QAAO,MAAM;;EAMjB,QAAW,OAAoB;;GAC7B,MAAM,QAAQ,SAAS,OAAO,UAAU;AACxC,OAAI,UAAU,UACZ,QAAO;AAGT,OAAI,MAAM,WACR,QAAO,MAAM;AAGf,SAAM,IAAI,cACR,mCAAU,MAAM,OAAO,oFAAe,GAAG,mBAC1C;;EAEJ;CAED,SAAS,SACP,OACA,QACsB;EACtB,MAAM,QAAQ,OAAO,IAAI,MAAM,OAAO;EACtC,MAAM,WAAW,UAAU,UAAa,OAAO,IAAI,MAAM,OAAO;AAEhE,MAAI,YAAY,WAAW,UACzB,QAAO;EAGT,MAAM,iBAAiB,UAAU,IAAI,MAAM,OAAO;AAClD,MAAI,eAGF,SAFc,SAAS,eAAe,QAAQ,EAE9C;GACE,KAAK,YACH,KAAI,SACF,QAAO;YACE,0BAA0B,SAAS,MAAM,CAClD;QACK;IAEL,MAAM,QAAQ,eAAe,QAAQ,UAAU;AAC/C,cAAU,UAAU,OAAO,MAAM;AACjC,WAAO;;GAIX,KAAK,SACH,KAAI,SACF,QAAO;QACF;IAEL,MAAM,QAAQ,eAAe,QAAQ,UAAU;AAC/C,cAAU,UAAU,OAAO,MAAM;AACjC,WAAO;;GAIX,KAAK,YAEH,QAAO,eAAe,QAAQ,OAAO;;AAK3C,MAAI,SACF,QAAO;AAGT,MAAI,QACF,QAAO,6BAA6B,SAAS,OAAO,OAAO;AAG7D,SAAO;;CAGT,SAAS,iBACP,aACA,SACA;EACA,MAAM,YAAY,aAAa,QAAQ;AACvC,MAAI,WAAW;GACb,MAAM,QAAQ,OAAO,IAAI,YAAY;AACrC,OAAI,UAAU,UAAa,OAAO,IAAI,YAAY,CAChD,WAAU,MAAM;;;CAKtB,SAAS,qBAAqB;AAC5B,SAAO,IAAI,UAAU,QAAQ,UAAU;AACvC,SAAO,IAAI,SAAS,QAAQ,SAAS;AACrC,SAAO,IAAI,cAAc,QAAQ,UAAU;AAE3C,MAAI,QACF,QAAO,IAAI,kBAAkB,QAAQ,QAAQ;;AAIjD,qBAAoB;AACpB,QAAO;;AAGT,SAAS,0BACP,SACA,OACS;AACT,KAAI,CAAC,QAAS,QAAO;AAErB,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAClC,KAAI,QAAQ,GAAG,SAAS,MAAM,CAC5B,QAAO;AAIX,QAAO;;AAGT,SAAS,6BACP,SACA,OACA,QACsB;AACtB,MAAK,MAAM,mBAAmB,SAAS;EACrC,MAAM,iBAAiB,gBAAgB,IAAI,SAAS;AACpD,MAAI,gBAAgB;GAClB,MAAM,WAAW,eAAe,OAAO,OAAO;AAC9C,OAAI,aAAa,UACf,QAAO;;;AAKb,QAAO;;;;;;;;AC7WT,SAAgB,QAAW,OAAmC;AAC5D,QACE,UAAU,UACV,UAAU,QACV,OAAO,UAAU,YACjB,YAAY,SACZ,OAAQ,MAAc,WAAW;;;;;;;;AAUrC,SAAgB,eACd,WACA,OACA,OACM;;CAEN,MAAM,aAAa,CAAC,qBADD,UAAU,IAAI,MAAM,2DAAI,EAAE,EACV,MAAM;AACzC,WAAU,UAAU,OAAO,WAAW;;;;;;;;;;;;;;;;;;;AAoBxC,SAAgB,gBAOd,WAAsB,OAAuB;AAC7C,KAAI,QAAQ,MAAM,CAChB,QAAO,UAAU,IAAI,MAAM;CAG7B,MAAM,MAAW,EAAE;AACnB,QAAO,KAAK,MAAM,CAAC,SAAS,QAAS,IAAI,OAAO,UAAU,IAAI,MAAM,KAAK,CAAE;AAC3E,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAgB,iBASd,WAAsB,GAAG,QAAwB;AACjD,QAAO,OAAO,KAAK,SAAS,gBAAgB,WAAW,KAAK,CAAC;;;;;;;;;;;;;;;;;;;AAoB/D,SAAgB,aAOd,WAAsB,OAAuB;AAC7C,KAAI,QAAQ,MAAM,CAChB,QAAO,UAAU,QAAQ,MAAM;CAGjC,MAAM,MAAW,EAAE;AACnB,QAAO,KAAK,MAAM,CAAC,SAChB,QAAS,IAAI,OAAO,UAAU,QAAQ,MAAM,KAAK,CACnD;AACD,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAgB,cASd,WAAsB,GAAG,QAAwB;AACjD,QAAO,OAAO,KAAK,SAAS,aAAa,WAAW,KAAK,CAAC;;;;;;;;;;;;;AAc5D,SAAgB,WAYd,SACA,GAAG,QAC+B;AAClC,SAAQ,cAAc;EACpB,MAAM,SAAiB,cAAc,WAAW,GAAG,OAAO;AAC1D,SAAO,QAAQ,MAAM,MAAM,OAAO;;;;;;;;;;;;;;;AAgBtC,SAAgB,gBAYd,aACA,GAAG,QAC+B;AAClC,QAAO,YACJ,GAAG,WAAW,IAAI,YAAY,GAAG,OAAO,EACzC,GAAG,OACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxHH,SAAgB,WACd,WACA,mBACA,SACM;CACN,MAAM,YAAgC;EACpC,QAAQ;EACR,SAAS,mDAAW,EAAE;EACvB;CAED,MAAM,YAAY,IAAI,IAAwB,CAAC,UAAU,CAAC;CAC1D,MAAM,WAAiC,CAAC,UAAU;CAElD,IAAI,WAAW;AACf,QAAO,WAAW,SAAS,QAAQ;;EACjC,MAAM,QAAQ,SAAS;AAIvB,iBAFU,YAAY,QAAQ,MAAM,SAAS,OAE3C,yDAAS,SAAS,aAAa;AAC/B,OAAI,CAAC,UAAU,IAAI,SAAS,EAAE;AAC5B,cAAU,IAAI,SAAS;AACvB,aAAS,KAAK,SAAS;;IAEzB;AAEF;;AAGF,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;;EACxC,MAAM,QAAQ,SAAS;EACvB,MAAM,IAAI,YAAY,QAAQ,MAAM,SAAS;AAC7C,wBAAE,mFAAgB,UAAU;;AAG9B,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,QAAQ,SAAS;AACvB,kBAAgB,WAAW,MAAM;;AAGnC,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;;EAC7C,MAAM,QAAQ,SAAS;EACvB,MAAM,IAAI,YAAY,QAAQ,MAAM,SAAS;AAC7C,uBAAE,gFAAe,UAAU;;AAG7B,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,QAAQ,SAAS;EACvB,MAAM,IAAI,YAAY,QAAQ,MAAM,SAAS;AAC7C,MAAI,EAAE,aAAa,QACjB,WAAU,QAAQ,EAAE,MAAM;;;AAKhC,SAAS,gBACP,WACA,OACM;CACN,IAAI;CACJ,IAAI;AAEJ,KAAI,YAAY,OAAO;AACrB,WAAS,MAAM;AACf,YAAU,MAAM;OAEhB,UAAS;CAGX,MAAM,0DAAQ,QAAS;CACvB,MAAM,sCAAsB,IAAI,KAAqB;CACrD,MAAM,gBAAgB,OAAO;AAE7B,KAAI,cAGF,CAFa,OAAO,KAAK,cAAc,CAElC,SAAS,aAAa;EACzB,MAAM,aAAa,cAAc;AACjC,MAAI,YAAY;AACd,uBAAoB,IAAI,WAAW;AAEnC,aAAU,YACR,YACA,YAAY,WAAW,OAAO,WAAW,OAAO,MAAM,EACtD,EAAE,OAAO,CACV;;GAEH;AAGJ,WAAU,YAAY,OAAO,OAAO,OAAO,SAAS;EAClD;EACA,YAAY,mBAAmB;AAC7B,OAAI,eAAe,QACjB,gBAAe,SAAS;AAG1B,uBAAoB,SAAS,eAAe,UAAU,OAAO,WAAW,CAAC;AACzE,uBAAoB,OAAO;;EAE9B,CAAC;;;;;;;;AASJ,SAAgB,YACd,WACA,SACM;AACN,SAAQ,SAAS,UAAU;AACzB,MAAI,YAAY,MACd,YAAW,WAAW,MAAM,QAAQ,MAAM,QAAQ;MAElD,YAAW,WAAW,MAAM;GAE9B;;;;;;;;;;;;;;;;;;;;;;;;AAyBJ,SAAgB,cACd,aAEsB;;AACtB,0CAAY,oBAAa,6BAAO,YAAY,wEAAS,OAAO;;;;;;;;;AAU9D,SAAgB,sBACd,SAC2B;AAC3B,QAAO,cAAc;EACnB,gBAAgB,EAAE;EAClB,SAAS;EACV,CAAC"}
|
package/dist/index.umd.js
CHANGED
|
@@ -87,12 +87,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
87
87
|
factories.delete(token.symbol);
|
|
88
88
|
},
|
|
89
89
|
removeAll() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
let hasError = false;
|
|
91
|
+
let firstError;
|
|
92
|
+
try {
|
|
93
|
+
factories.forEach((context, tokenSymbol) => {
|
|
94
|
+
try {
|
|
95
|
+
if (context.options) executeOnRemoved(tokenSymbol, context.options);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (!hasError) {
|
|
98
|
+
hasError = true;
|
|
99
|
+
firstError = error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
} finally {
|
|
104
|
+
values.clear();
|
|
105
|
+
factories.clear();
|
|
106
|
+
bindInternalTokens();
|
|
107
|
+
}
|
|
108
|
+
if (hasError) throw firstError;
|
|
96
109
|
},
|
|
97
110
|
hasToken(token) {
|
|
98
111
|
return values.has(token.symbol) || factories.has(token.symbol) || parentContainersHaveToken(parents, token);
|
package/dist/index.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/tokens.ts","../src/container.ts","../src/utils.ts","../src/modules.ts"],"sourcesContent":["/**\n * @ignore\n * Binding token for mandatory value\n */\nexport type RequiredToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional?: false;\n};\n\n/**\n * @ignore\n * Binding token for optional value\n */\nexport type OptionalToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional: true;\n optionalValue: T;\n};\n\n/**\n * Binding token\n */\nexport type Token<T> = RequiredToken<T> | OptionalToken<T>;\n\n/**\n * Token options\n */\nexport type TokenOptions =\n | {\n /**\n * Key for token's symbol. It allows to create shareable tokens.\n */\n key: string;\n\n /** @ignore */\n description?: undefined;\n }\n | {\n /** Description for better error messages */\n description?: string;\n\n /** @ignore */\n key?: undefined;\n };\n\n/**\n * Creates a new binding token.\n * @param description - Token description for better error messages.\n */\nexport function token<T>(description?: string): Token<T>;\n/**\n * Creates a new binding token.\n * @param options - Token description for better error messages.\n */ export function token<T>(options?: TokenOptions): Token<T>;\nexport function token<T>(options?: TokenOptions | string): Token<T> {\n const normalized: TokenOptions | undefined =\n typeof options === 'string' ? { description: options } : options;\n\n const symbol: symbol = normalized?.key\n ? Symbol.for(normalized.key)\n : Symbol(normalized?.description);\n\n return { symbol };\n}\n\n/**\n * Decorate a token with an optional value.\n * This value is be used as default value in case a container does not have registered token.\n * @param token - Existed token.\n * @param optionalValue - Default value for the resolver.\n */\nexport function optional<T>(\n token: Token<T>,\n optionalValue: T,\n): OptionalToken<T>;\nexport function optional<T>(token: Token<T>): OptionalToken<T | undefined>;\nexport function optional<T>(\n token: Token<T>,\n optionalValue?: T,\n): OptionalToken<T | undefined> {\n return {\n symbol: token.symbol,\n isOptional: true,\n optionalValue,\n };\n}\n","import { Token, token } from './tokens';\n\n/**\n * ResolverError is thrown by the resolver when a token is not found in a container.\n */\nexport class ResolverError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = 'ResolverError';\n }\n}\n\n/**\n * @see https://github.com/mnasyrov/ditox#factory-lifetimes\n */\nexport type FactoryScope = 'scoped' | 'singleton' | 'transient';\n\n/**\n * Options for factory binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n * - `transient` - The value is created every time it is resolved.\n *\n * `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.\n */\nexport type FactoryOptions<T> =\n | {\n scope?: 'scoped' | 'singleton';\n onRemoved?: (value: T) => void;\n }\n | {\n scope: 'transient';\n };\n\n/**\n * Dependency container.\n */\nexport type Container = {\n /**\n * Binds a value for the token\n */\n bindValue<T>(token: Token<T>, value: T): void;\n\n /**\n * Binds a factory for the token.\n */\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void;\n\n /**\n * Checks if the token is registered in the container hierarchy.\n */\n hasToken(token: Token<unknown>): boolean;\n\n /**\n * Returns a resolved value by the token, or returns `undefined` in case the token is not found.\n */\n get<T>(token: Token<T>): T | undefined;\n\n /**\n * Returns a resolved value by the token or throws `ResolverError` in case the token is not found.\n */\n resolve<T>(token: Token<T>): T;\n\n /**\n * Removes a binding for the token.\n */\n remove<T>(token: Token<T>): void;\n\n /**\n * Removes all bindings in the container.\n */\n removeAll(): void;\n};\n\n/**\n * A subset of Container interface that provides read-only access to dependency resolution.\n * This type is used for parent containers to allow token resolution without exposing mutation methods.\n */\nexport type ContainerResolver = Pick<Container, 'hasToken' | 'get' | 'resolve'>;\n\n/** @internal */\nexport const CONTAINER: Token<Container> = token('ditox.Container');\n/** @internal */\nexport const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(\n 'ditox.ParentContainer',\n);\n/** @internal */\nexport const RESOLVER: Token<Resolver> = token('ditox.Resolver');\n\n/** @internal */\nconst NOT_FOUND = Symbol();\n\n/** @internal */\nconst DEFAULT_SCOPE: FactoryScope = 'singleton';\n\n/** @internal */\ntype FactoryContext<T> = {\n factory: (container: Container) => T;\n options?: FactoryOptions<T>;\n};\n\n/** @internal */\ntype ValuesMap = Map<symbol, any>;\n\n/** @internal */\nexport type FactoriesMap = Map<symbol, FactoryContext<any>>;\n\n/** @internal */\nexport const FACTORIES_MAP: Token<FactoriesMap> = token('ditox.FactoriesMap');\n\n/** @internal */\ntype Resolver = <T>(token: Token<T>, origin: Container) => T | typeof NOT_FOUND;\n\n/** @internal */\nfunction getScope<T>(options?: FactoryOptions<T>): FactoryScope {\n return options?.scope ?? DEFAULT_SCOPE;\n}\n\n/** @internal */\nfunction getOnRemoved<T>(options: FactoryOptions<T>) {\n return options.scope === undefined ||\n options.scope === 'scoped' ||\n options.scope === 'singleton'\n ? options.onRemoved\n : undefined;\n}\n\n/** @internal */\nfunction isInternalToken<T>(token: Token<T>): boolean {\n return (\n token.symbol === CONTAINER.symbol ||\n token.symbol === PARENT_CONTAINERS.symbol ||\n token.symbol === RESOLVER.symbol\n );\n}\n\n/**\n * Creates a new dependency container.\n *\n * Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.\n *\n * @param parentArg - Optional parent container or an array of containers.\n */\nexport function createContainer(\n parentArg?: ContainerResolver | ReadonlyArray<ContainerResolver>,\n): Container {\n const parents: ReadonlyArray<ContainerResolver> | undefined = parentArg\n ? Array.isArray(parentArg)\n ? [...parentArg]\n : [parentArg]\n : undefined;\n\n const values: ValuesMap = new Map<symbol, any>();\n const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();\n\n const container: Container = {\n bindValue<T>(token: Token<T>, value: T): void {\n if (isInternalToken(token)) {\n return;\n }\n\n values.set(token.symbol, value);\n },\n\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void {\n if (isInternalToken(token)) {\n return;\n }\n\n factories.set(token.symbol, { factory, options });\n },\n\n remove<T>(token: Token<T>): void {\n if (isInternalToken(token)) {\n return;\n }\n\n const options = factories.get(token.symbol)?.options;\n if (options) {\n executeOnRemoved(token.symbol, options);\n }\n\n values.delete(token.symbol);\n factories.delete(token.symbol);\n },\n\n removeAll(): void {\n factories.forEach((context, tokenSymbol) => {\n if (context.options) {\n executeOnRemoved(tokenSymbol, context.options);\n }\n });\n\n values.clear();\n factories.clear();\n bindInternalTokens();\n },\n\n hasToken(token: Token<unknown>): boolean {\n return (\n values.has(token.symbol) ||\n factories.has(token.symbol) ||\n parentContainersHaveToken(parents, token)\n );\n },\n\n get<T>(token: Token<T>): T | undefined {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n return undefined;\n },\n\n resolve<T>(token: Token<T>): T {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n throw new ResolverError(\n `Token \"${token.symbol.description ?? ''}\" is not provided`,\n );\n },\n };\n\n function resolver<T>(\n token: Token<T>,\n origin: Container,\n ): T | typeof NOT_FOUND {\n const value = values.get(token.symbol);\n const hasValue = value !== undefined || values.has(token.symbol);\n\n if (hasValue && origin === container) {\n return value;\n }\n\n const factoryContext = factories.get(token.symbol);\n if (factoryContext) {\n const scope = getScope(factoryContext.options);\n\n switch (scope) {\n case 'singleton': {\n if (hasValue) {\n return value;\n } else if (parentContainersHaveToken(parents, token)) {\n break;\n } else {\n // Cache the value in the same container where the factory is registered.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'scoped': {\n if (hasValue) {\n return value;\n } else {\n // Create a value within the factory's container and cache it.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'transient': {\n // Create a value within the origin container and don't cache it.\n return factoryContext.factory(origin);\n }\n }\n }\n\n if (hasValue) {\n return value;\n }\n\n if (parents) {\n return parentContainersResolveToken(parents, token, origin);\n }\n\n return NOT_FOUND;\n }\n\n function executeOnRemoved<T>(\n tokenSymbol: symbol,\n options: FactoryOptions<T>,\n ) {\n const onRemoved = getOnRemoved(options);\n if (onRemoved) {\n const value = values.get(tokenSymbol);\n if (value !== undefined || values.has(tokenSymbol)) {\n onRemoved(value);\n }\n }\n }\n\n function bindInternalTokens() {\n values.set(CONTAINER.symbol, container);\n values.set(RESOLVER.symbol, resolver);\n values.set(FACTORIES_MAP.symbol, factories);\n\n if (parents) {\n values.set(PARENT_CONTAINERS.symbol, parents);\n }\n }\n\n bindInternalTokens();\n return container;\n}\n\nfunction parentContainersHaveToken<T>(\n parents: ReadonlyArray<ContainerResolver> | undefined,\n token: Token<T>,\n): boolean {\n if (!parents) return false;\n\n for (let i = 0; i < parents.length; i++) {\n if (parents[i].hasToken(token)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parentContainersResolveToken<T>(\n parents: ReadonlyArray<ContainerResolver>,\n token: Token<T>,\n origin: Container,\n): T | typeof NOT_FOUND {\n for (const parentContainer of parents) {\n const parentResolver = parentContainer.get(RESOLVER);\n if (parentResolver) {\n const resolved = parentResolver(token, origin);\n if (resolved !== NOT_FOUND) {\n return resolved;\n }\n }\n }\n\n return NOT_FOUND;\n}\n","import type { Container } from './container';\nimport { Token } from './tokens';\n\ntype ValuesProps = { [key: string]: unknown };\ntype TokenProps<Props extends ValuesProps> = {\n [K in keyof Props]: Token<Props[K]>;\n};\n\n/**\n * Checks if a value is the token\n */\nexport function isToken<T>(value: unknown): value is Token<T> {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n 'symbol' in value &&\n typeof (value as any).symbol === 'symbol'\n );\n}\n\n/**\n * Rebinds the array by the token with added new value.\n * @param container - Dependency container.\n * @param token - Token for an array of values.\n * @param value - New value which is added to the end of the array.\n */\nexport function bindMultiValue<T>(\n container: Container,\n token: Token<ReadonlyArray<T>>,\n value: T,\n): void {\n const prevValues = container.get(token) ?? [];\n const nextValues = [...prevValues, value];\n container.bindValue(token, nextValues);\n}\n\n/**\n * Tries to resolve a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const value = tryResolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = tryResolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function tryResolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V | undefined\n : Tokens extends TokenProps<infer Props>\n ? Partial<Props>\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.get(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach((key) => (obj[key] = container.get(token[key])));\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const items1 = tryResolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = tryResolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function tryResolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V | undefined\n : Tokens[K] extends TokenProps<infer Props>\n ? Partial<Props>\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => tryResolveValue(container, item)) as Values;\n}\n\n/**\n * Resolves a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a value is not found by the token, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const value = resolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = resolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function resolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V\n : Tokens extends TokenProps<infer Props>\n ? Props\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.resolve(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach(\n (key) => (obj[key] = container.resolve(token[key])),\n );\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const items1 = resolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = resolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function resolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => resolveValue(container, item)) as Values;\n}\n\n/**\n * Decorates a factory by passing resolved values as factory arguments.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param factory - A factory.\n * @param tokens - Tokens which correspond to factory arguments.\n *\n * @return Decorated factory which takes a dependency container as a single argument.\n */\nexport function injectable<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n factory: (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return (container) => {\n const values: Values = resolveValues(container, ...tokens);\n return factory.apply(this, values);\n };\n}\n\n/**\n * Decorates a class by passing resolved values as arguments to its constructor.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param constructor - Constructor of a class\n * @param tokens - Tokens which correspond to constructor arguments\n *\n * @return A factory function which takes a dependency container as a single argument\n * and returns a new created class.\n */\nexport function injectableClass<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n constructor: new (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return injectable<Tokens, Values, Result>(\n (...values) => new constructor(...values),\n ...tokens,\n );\n}\n","import { Container } from './container';\nimport { Token, token } from './tokens';\nimport { injectable } from './utils';\n\ntype AnyObject = Record<string, any>;\ntype EmptyObject = Record<string, never>;\n\ntype ModuleController = {\n /** Dispose the module and clean its resources */\n destroy?: () => void;\n};\n\n/**\n * Dependency module\n *\n * @example\n * ```ts\n * type LoggerModule = Module<{\n * logger: Logger;\n * }>;\n * ```\n */\nexport type Module<ModuleProps extends AnyObject = EmptyObject> =\n ModuleController & ModuleProps;\n\ntype GetModuleProps<T> = T extends Module<infer Props> ? Props : never;\n\n/**\n * Description how to bind the module in declarative way.\n *\n * @example\n * ```ts\n * const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {\n * token: LOGGER_MODULE_TOKEN,\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * };\n * ```\n */\nexport type ModuleDeclaration<T extends Module<AnyObject>> = {\n /** Token for the module */\n token: Token<T>;\n\n /** Modules for binding */\n imports?: ReadonlyArray<ModuleBindingEntry>;\n\n /** Factory of the module */\n factory: (container: Container) => T;\n\n /** Dictionary of module properties which are bound to tokens. */\n exports?: {\n [K in keyof GetModuleProps<T>]?: Token<GetModuleProps<T>[K]>;\n };\n\n /** Callback could be used to prepare an environment. It is called before binding the module. */\n beforeBinding?: (container: Container) => void;\n\n /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */\n afterBinding?: (container: Container) => void;\n\n /**\n * Strategy for executing the factory:\n * - `lazy` - **This is the default**. The factory is called when the module is resolved.\n * - `eager` - The factory is called immediately after the module is bound to the container.\n */\n strategy?: 'eager' | 'lazy';\n};\n\nexport type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;\n\n/**\n * Options for module binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n */\nexport type BindModuleOptions = {\n scope?: 'scoped' | 'singleton';\n};\n\ntype ModuleDeclarationWithOptions = {\n module: ModuleDeclaration<AnyObject>;\n options: BindModuleOptions;\n};\n\nexport type ModuleBindingEntry =\n | ModuleDeclaration<AnyObject>\n | ModuleDeclarationWithOptions;\n\n/**\n * Binds the dependency module to the container\n * @param container - Dependency container.\n * @param moduleDeclaration - Declaration of the dependency module.\n * @param options - Options for module binding.\n *\n * @example\n * ```ts\n * bindModule(container, LOGGER_MODULE);\n * ```\n */\nexport function bindModule<T extends Module<AnyObject>>(\n container: Container,\n moduleDeclaration: ModuleDeclaration<T>,\n options?: BindModuleOptions,\n): void {\n const rootEntry: ModuleBindingEntry = {\n module: moduleDeclaration,\n options: options ?? {},\n };\n\n const bfsVisits = new Set<ModuleBindingEntry>([rootEntry]);\n const bfsQueue: ModuleBindingEntry[] = [rootEntry];\n\n let bfsIndex = 0;\n while (bfsIndex < bfsQueue.length) {\n const entry = bfsQueue[bfsIndex];\n\n const m = 'module' in entry ? entry.module : entry;\n\n m.imports?.forEach((depEntry) => {\n if (!bfsVisits.has(depEntry)) {\n bfsVisits.add(depEntry);\n bfsQueue.push(depEntry);\n }\n });\n\n bfsIndex++;\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.beforeBinding?.(container);\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n bindModuleEntry(container, entry);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.afterBinding?.(container);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n if (m.strategy === 'eager') {\n container.resolve(m.token);\n }\n }\n}\n\nfunction bindModuleEntry(\n container: Container,\n entry: ModuleBindingEntry,\n): void {\n let module: ModuleDeclaration<AnyObject>;\n let options: BindModuleOptions | undefined;\n\n if ('module' in entry) {\n module = entry.module;\n options = entry.options;\n } else {\n module = entry;\n }\n\n const scope = options?.scope;\n const exportedValueTokens = new Set<Token<unknown>>();\n const moduleExports = module.exports;\n\n if (moduleExports) {\n const keys = Object.keys(moduleExports);\n\n keys.forEach((valueKey) => {\n const valueToken = moduleExports[valueKey];\n if (valueToken) {\n exportedValueTokens.add(valueToken);\n\n container.bindFactory(\n valueToken,\n injectable((module) => module[valueKey], module.token),\n { scope },\n );\n }\n });\n }\n\n container.bindFactory(module.token, module.factory, {\n scope,\n onRemoved: (moduleInstance) => {\n if (moduleInstance.destroy) {\n moduleInstance.destroy();\n }\n\n exportedValueTokens.forEach((valueToken) => container.remove(valueToken));\n exportedValueTokens.clear();\n },\n });\n}\n\n/**\n * Binds dependency modules to the container\n *\n * @param container - Dependency container for binding\n * @param modules - Array of module binding entries: module declaration or `{module: ModuleDeclaration, options: BindModuleOptions}` objects.\n */\nexport function bindModules(\n container: Container,\n modules: ReadonlyArray<ModuleBindingEntry>,\n): void {\n modules.forEach((entry) => {\n if ('module' in entry) {\n bindModule(container, entry.module, entry.options);\n } else {\n bindModule(container, entry);\n }\n });\n}\n\n/**\n * Declares a module binding\n *\n * @param declaration - a module declaration\n * @param declaration.token - optional field\n *\n * @example\n * ```ts\n * const LOGGER_MODULE = declareModule<LoggerModule>({\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * });\n * ```\n */\nexport function declareModule<T extends Module<AnyObject>>(\n declaration: Omit<ModuleDeclaration<T>, 'token'> &\n Partial<Pick<ModuleDeclaration<T>, 'token'>>,\n): ModuleDeclaration<T> {\n return { ...declaration, token: declaration.token ?? token() };\n}\n\n/**\n * @deprecated Use `declareModule` instead\n *\n * Declares bindings of several modules\n *\n * @param modules - module declaration entries\n */\nexport function declareModuleBindings(\n modules: ReadonlyArray<ModuleBindingEntry>,\n): ModuleDeclaration<Module> {\n return declareModule({\n factory: () => ({}),\n imports: modules,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/tokens.ts","../src/container.ts","../src/utils.ts","../src/modules.ts"],"sourcesContent":["/**\n * @ignore\n * Binding token for mandatory value\n */\nexport type RequiredToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional?: false;\n};\n\n/**\n * @ignore\n * Binding token for optional value\n */\nexport type OptionalToken<T> = {\n symbol: symbol;\n type?: T; // Anchor for Typescript type inference.\n isOptional: true;\n optionalValue: T;\n};\n\n/**\n * Binding token\n */\nexport type Token<T> = RequiredToken<T> | OptionalToken<T>;\n\n/**\n * Token options\n */\nexport type TokenOptions =\n | {\n /**\n * Key for token's symbol. It allows to create shareable tokens.\n */\n key: string;\n\n /** @ignore */\n description?: undefined;\n }\n | {\n /** Description for better error messages */\n description?: string;\n\n /** @ignore */\n key?: undefined;\n };\n\n/**\n * Creates a new binding token.\n * @param description - Token description for better error messages.\n */\nexport function token<T>(description?: string): Token<T>;\n/**\n * Creates a new binding token.\n * @param options - Token description for better error messages.\n */ export function token<T>(options?: TokenOptions): Token<T>;\nexport function token<T>(options?: TokenOptions | string): Token<T> {\n const normalized: TokenOptions | undefined =\n typeof options === 'string' ? { description: options } : options;\n\n const symbol: symbol = normalized?.key\n ? Symbol.for(normalized.key)\n : Symbol(normalized?.description);\n\n return { symbol };\n}\n\n/**\n * Decorate a token with an optional value.\n * This value is be used as default value in case a container does not have registered token.\n * @param token - Existed token.\n * @param optionalValue - Default value for the resolver.\n */\nexport function optional<T>(\n token: Token<T>,\n optionalValue: T,\n): OptionalToken<T>;\nexport function optional<T>(token: Token<T>): OptionalToken<T | undefined>;\nexport function optional<T>(\n token: Token<T>,\n optionalValue?: T,\n): OptionalToken<T | undefined> {\n return {\n symbol: token.symbol,\n isOptional: true,\n optionalValue,\n };\n}\n","import { Token, token } from './tokens';\n\n/**\n * ResolverError is thrown by the resolver when a token is not found in a container.\n */\nexport class ResolverError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n this.name = 'ResolverError';\n }\n}\n\n/**\n * @see https://github.com/mnasyrov/ditox#factory-lifetimes\n */\nexport type FactoryScope = 'scoped' | 'singleton' | 'transient';\n\n/**\n * Options for factory binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n * - `transient` - The value is created every time it is resolved.\n *\n * `scoped` and `singleton` scopes can have `onRemoved` callback. It is called when a token is removed from the container.\n */\nexport type FactoryOptions<T> =\n | {\n scope?: 'scoped' | 'singleton';\n onRemoved?: (value: T) => void;\n }\n | {\n scope: 'transient';\n };\n\n/**\n * Dependency container.\n */\nexport type Container = {\n /**\n * Binds a value for the token\n */\n bindValue<T>(token: Token<T>, value: T): void;\n\n /**\n * Binds a factory for the token.\n */\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void;\n\n /**\n * Checks if the token is registered in the container hierarchy.\n */\n hasToken(token: Token<unknown>): boolean;\n\n /**\n * Returns a resolved value by the token, or returns `undefined` in case the token is not found.\n */\n get<T>(token: Token<T>): T | undefined;\n\n /**\n * Returns a resolved value by the token or throws `ResolverError` in case the token is not found.\n */\n resolve<T>(token: Token<T>): T;\n\n /**\n * Removes a binding for the token.\n */\n remove<T>(token: Token<T>): void;\n\n /**\n * Removes all bindings in the container.\n * Continues after cleanup failures and rethrows the first thrown value unchanged\n * after clearing bindings and restoring internal tokens. Later failures are discarded.\n */\n removeAll(): void;\n};\n\n/**\n * A subset of Container interface that provides read-only access to dependency resolution.\n * This type is used for parent containers to allow token resolution without exposing mutation methods.\n */\nexport type ContainerResolver = Pick<Container, 'hasToken' | 'get' | 'resolve'>;\n\n/** @internal */\nexport const CONTAINER: Token<Container> = token('ditox.Container');\n/** @internal */\nexport const PARENT_CONTAINERS: Token<ReadonlyArray<Container>> = token(\n 'ditox.ParentContainer',\n);\n/** @internal */\nexport const RESOLVER: Token<Resolver> = token('ditox.Resolver');\n\n/** @internal */\nconst NOT_FOUND = Symbol();\n\n/** @internal */\nconst DEFAULT_SCOPE: FactoryScope = 'singleton';\n\n/** @internal */\ntype FactoryContext<T> = {\n factory: (container: Container) => T;\n options?: FactoryOptions<T>;\n};\n\n/** @internal */\ntype ValuesMap = Map<symbol, any>;\n\n/** @internal */\nexport type FactoriesMap = Map<symbol, FactoryContext<any>>;\n\n/** @internal */\nexport const FACTORIES_MAP: Token<FactoriesMap> = token('ditox.FactoriesMap');\n\n/** @internal */\ntype Resolver = <T>(token: Token<T>, origin: Container) => T | typeof NOT_FOUND;\n\n/** @internal */\nfunction getScope<T>(options?: FactoryOptions<T>): FactoryScope {\n return options?.scope ?? DEFAULT_SCOPE;\n}\n\n/** @internal */\nfunction getOnRemoved<T>(options: FactoryOptions<T>) {\n return options.scope === undefined ||\n options.scope === 'scoped' ||\n options.scope === 'singleton'\n ? options.onRemoved\n : undefined;\n}\n\n/** @internal */\nfunction isInternalToken<T>(token: Token<T>): boolean {\n return (\n token.symbol === CONTAINER.symbol ||\n token.symbol === PARENT_CONTAINERS.symbol ||\n token.symbol === RESOLVER.symbol\n );\n}\n\n/**\n * Creates a new dependency container.\n *\n * Container can have an optional parent to chain token resolution. The parent is used in case the current container does not have a registered token.\n *\n * @param parentArg - Optional parent container or an array of containers.\n */\nexport function createContainer(\n parentArg?: ContainerResolver | ReadonlyArray<ContainerResolver>,\n): Container {\n const parents: ReadonlyArray<ContainerResolver> | undefined = parentArg\n ? Array.isArray(parentArg)\n ? [...parentArg]\n : [parentArg]\n : undefined;\n\n const values: ValuesMap = new Map<symbol, any>();\n const factories: FactoriesMap = new Map<symbol, FactoryContext<any>>();\n\n const container: Container = {\n bindValue<T>(token: Token<T>, value: T): void {\n if (isInternalToken(token)) {\n return;\n }\n\n values.set(token.symbol, value);\n },\n\n bindFactory<T>(\n token: Token<T>,\n factory: (container: Container) => T,\n options?: FactoryOptions<T>,\n ): void {\n if (isInternalToken(token)) {\n return;\n }\n\n factories.set(token.symbol, { factory, options });\n },\n\n remove<T>(token: Token<T>): void {\n if (isInternalToken(token)) {\n return;\n }\n\n const options = factories.get(token.symbol)?.options;\n if (options) {\n executeOnRemoved(token.symbol, options);\n }\n\n values.delete(token.symbol);\n factories.delete(token.symbol);\n },\n\n removeAll(): void {\n // A separate flag preserves thrown values such as undefined or false.\n let hasError = false;\n let firstError: unknown;\n try {\n factories.forEach((context, tokenSymbol) => {\n try {\n if (context.options) {\n executeOnRemoved(tokenSymbol, context.options);\n }\n } catch (error) {\n if (!hasError) {\n hasError = true;\n firstError = error;\n }\n }\n });\n } finally {\n values.clear();\n factories.clear();\n bindInternalTokens();\n }\n if (hasError) throw firstError;\n },\n\n hasToken(token: Token<unknown>): boolean {\n return (\n values.has(token.symbol) ||\n factories.has(token.symbol) ||\n parentContainersHaveToken(parents, token)\n );\n },\n\n get<T>(token: Token<T>): T | undefined {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n return undefined;\n },\n\n resolve<T>(token: Token<T>): T {\n const value = resolver(token, container);\n if (value !== NOT_FOUND) {\n return value;\n }\n\n if (token.isOptional) {\n return token.optionalValue;\n }\n\n throw new ResolverError(\n `Token \"${token.symbol.description ?? ''}\" is not provided`,\n );\n },\n };\n\n function resolver<T>(\n token: Token<T>,\n origin: Container,\n ): T | typeof NOT_FOUND {\n const value = values.get(token.symbol);\n const hasValue = value !== undefined || values.has(token.symbol);\n\n if (hasValue && origin === container) {\n return value;\n }\n\n const factoryContext = factories.get(token.symbol);\n if (factoryContext) {\n const scope = getScope(factoryContext.options);\n\n switch (scope) {\n case 'singleton': {\n if (hasValue) {\n return value;\n } else if (parentContainersHaveToken(parents, token)) {\n break;\n } else {\n // Cache the value in the same container where the factory is registered.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'scoped': {\n if (hasValue) {\n return value;\n } else {\n // Create a value within the factory's container and cache it.\n const value = factoryContext.factory(container);\n container.bindValue(token, value);\n return value;\n }\n }\n\n case 'transient': {\n // Create a value within the origin container and don't cache it.\n return factoryContext.factory(origin);\n }\n }\n }\n\n if (hasValue) {\n return value;\n }\n\n if (parents) {\n return parentContainersResolveToken(parents, token, origin);\n }\n\n return NOT_FOUND;\n }\n\n function executeOnRemoved<T>(\n tokenSymbol: symbol,\n options: FactoryOptions<T>,\n ) {\n const onRemoved = getOnRemoved(options);\n if (onRemoved) {\n const value = values.get(tokenSymbol);\n if (value !== undefined || values.has(tokenSymbol)) {\n onRemoved(value);\n }\n }\n }\n\n function bindInternalTokens() {\n values.set(CONTAINER.symbol, container);\n values.set(RESOLVER.symbol, resolver);\n values.set(FACTORIES_MAP.symbol, factories);\n\n if (parents) {\n values.set(PARENT_CONTAINERS.symbol, parents);\n }\n }\n\n bindInternalTokens();\n return container;\n}\n\nfunction parentContainersHaveToken<T>(\n parents: ReadonlyArray<ContainerResolver> | undefined,\n token: Token<T>,\n): boolean {\n if (!parents) return false;\n\n for (let i = 0; i < parents.length; i++) {\n if (parents[i].hasToken(token)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parentContainersResolveToken<T>(\n parents: ReadonlyArray<ContainerResolver>,\n token: Token<T>,\n origin: Container,\n): T | typeof NOT_FOUND {\n for (const parentContainer of parents) {\n const parentResolver = parentContainer.get(RESOLVER);\n if (parentResolver) {\n const resolved = parentResolver(token, origin);\n if (resolved !== NOT_FOUND) {\n return resolved;\n }\n }\n }\n\n return NOT_FOUND;\n}\n","import type { Container } from './container';\nimport { Token } from './tokens';\n\ntype ValuesProps = { [key: string]: unknown };\ntype TokenProps<Props extends ValuesProps> = {\n [K in keyof Props]: Token<Props[K]>;\n};\n\n/**\n * Checks if a value is the token\n */\nexport function isToken<T>(value: unknown): value is Token<T> {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n 'symbol' in value &&\n typeof (value as any).symbol === 'symbol'\n );\n}\n\n/**\n * Rebinds the array by the token with added new value.\n * @param container - Dependency container.\n * @param token - Token for an array of values.\n * @param value - New value which is added to the end of the array.\n */\nexport function bindMultiValue<T>(\n container: Container,\n token: Token<ReadonlyArray<T>>,\n value: T,\n): void {\n const prevValues = container.get(token) ?? [];\n const nextValues = [...prevValues, value];\n container.bindValue(token, nextValues);\n}\n\n/**\n * Tries to resolve a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const value = tryResolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = tryResolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function tryResolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V | undefined\n : Tokens extends TokenProps<infer Props>\n ? Partial<Props>\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.get(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach((key) => (obj[key] = container.get(token[key])));\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `undefined` value is used.\n *\n * @example\n * ```ts\n * const items1 = tryResolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = tryResolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function tryResolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V | undefined\n : Tokens[K] extends TokenProps<infer Props>\n ? Partial<Props>\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => tryResolveValue(container, item)) as Values;\n}\n\n/**\n * Resolves a value by the provided token.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a value is not found by the token, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const value = resolveValue(container, tokenA);\n * console.log(value); // 1\n *\n * const props = resolveValue(container, {a: tokenA, b: tokenB});\n * console.log(props); // {a: 1, b: 2}\n * ```\n */\nexport function resolveValue<\n Tokens extends Token<unknown> | { [key: string]: Token<unknown> },\n Values extends Tokens extends Token<infer V>\n ? V\n : Tokens extends TokenProps<infer Props>\n ? Props\n : never,\n>(container: Container, token: Tokens): Values {\n if (isToken(token)) {\n return container.resolve(token) as Values;\n }\n\n const obj: any = {};\n Object.keys(token).forEach(\n (key) => (obj[key] = container.resolve(token[key])),\n );\n return obj;\n}\n\n/**\n * Returns an array of resolved values or objects with resolved values.\n *\n * If an item of the array is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n\n * If a token is not found, then `ResolverError` is thrown.\n *\n * @example\n * ```ts\n * const items1 = resolveValues(container, tokenA);\n * console.log(items1); // [1]\n *\n * const items2 = resolveValues(container, tokenA, {a: tokenA, b: tokenB});\n * console.log(items2); // [1, {a: 1, b: 2}]\n * ```\n */\nexport function resolveValues<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n>(container: Container, ...tokens: Tokens): Values {\n return tokens.map((item) => resolveValue(container, item)) as Values;\n}\n\n/**\n * Decorates a factory by passing resolved values as factory arguments.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param factory - A factory.\n * @param tokens - Tokens which correspond to factory arguments.\n *\n * @return Decorated factory which takes a dependency container as a single argument.\n */\nexport function injectable<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n factory: (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return (container) => {\n const values: Values = resolveValues(container, ...tokens);\n return factory.apply(this, values);\n };\n}\n\n/**\n * Decorates a class by passing resolved values as arguments to its constructor.\n *\n * If an argument is an object which has tokens as its properties,\n * then returns an object containing resolved values as properties.\n *\n * @param constructor - Constructor of a class\n * @param tokens - Tokens which correspond to constructor arguments\n *\n * @return A factory function which takes a dependency container as a single argument\n * and returns a new created class.\n */\nexport function injectableClass<\n Tokens extends (Token<unknown> | { [key: string]: Token<unknown> })[],\n Values extends {\n [K in keyof Tokens]: Tokens[K] extends Token<infer V>\n ? V\n : Tokens[K] extends TokenProps<infer Props>\n ? Props\n : never;\n },\n Result,\n>(\n this: unknown,\n constructor: new (...params: Values) => Result,\n ...tokens: Tokens\n): (container: Container) => Result {\n return injectable<Tokens, Values, Result>(\n (...values) => new constructor(...values),\n ...tokens,\n );\n}\n","import { Container } from './container';\nimport { Token, token } from './tokens';\nimport { injectable } from './utils';\n\ntype AnyObject = Record<string, any>;\ntype EmptyObject = Record<string, never>;\n\ntype ModuleController = {\n /** Dispose the module and clean its resources */\n destroy?: () => void;\n};\n\n/**\n * Dependency module\n *\n * @example\n * ```ts\n * type LoggerModule = Module<{\n * logger: Logger;\n * }>;\n * ```\n */\nexport type Module<ModuleProps extends AnyObject = EmptyObject> =\n ModuleController & ModuleProps;\n\ntype GetModuleProps<T> = T extends Module<infer Props> ? Props : never;\n\n/**\n * Description how to bind the module in declarative way.\n *\n * @example\n * ```ts\n * const LOGGER_MODULE: ModuleDeclaration<LoggerModule> = {\n * token: LOGGER_MODULE_TOKEN,\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * };\n * ```\n */\nexport type ModuleDeclaration<T extends Module<AnyObject>> = {\n /** Token for the module */\n token: Token<T>;\n\n /** Modules for binding */\n imports?: ReadonlyArray<ModuleBindingEntry>;\n\n /** Factory of the module */\n factory: (container: Container) => T;\n\n /** Dictionary of module properties which are bound to tokens. */\n exports?: {\n [K in keyof GetModuleProps<T>]?: Token<GetModuleProps<T>[K]>;\n };\n\n /** Callback could be used to prepare an environment. It is called before binding the module. */\n beforeBinding?: (container: Container) => void;\n\n /** Callback could be used to export complex dependencies from the module. It is called after binding the module. */\n afterBinding?: (container: Container) => void;\n\n /**\n * Strategy for executing the factory:\n * - `lazy` - **This is the default**. The factory is called when the module is resolved.\n * - `eager` - The factory is called immediately after the module is bound to the container.\n */\n strategy?: 'eager' | 'lazy';\n};\n\nexport type AnyModuleDeclaration = ModuleDeclaration<Module<AnyObject>>;\n\n/**\n * Options for module binding.\n *\n * `scope` types:\n * - `singleton` - **This is the default**. The value is created and cached by the most distant parent container which owns the factory function.\n * - `scoped` - The value is created and cached by the nearest container which owns the factory function.\n */\nexport type BindModuleOptions = {\n scope?: 'scoped' | 'singleton';\n};\n\ntype ModuleDeclarationWithOptions = {\n module: ModuleDeclaration<AnyObject>;\n options: BindModuleOptions;\n};\n\nexport type ModuleBindingEntry =\n | ModuleDeclaration<AnyObject>\n | ModuleDeclarationWithOptions;\n\n/**\n * Binds the dependency module to the container\n * @param container - Dependency container.\n * @param moduleDeclaration - Declaration of the dependency module.\n * @param options - Options for module binding.\n *\n * @example\n * ```ts\n * bindModule(container, LOGGER_MODULE);\n * ```\n */\nexport function bindModule<T extends Module<AnyObject>>(\n container: Container,\n moduleDeclaration: ModuleDeclaration<T>,\n options?: BindModuleOptions,\n): void {\n const rootEntry: ModuleBindingEntry = {\n module: moduleDeclaration,\n options: options ?? {},\n };\n\n const bfsVisits = new Set<ModuleBindingEntry>([rootEntry]);\n const bfsQueue: ModuleBindingEntry[] = [rootEntry];\n\n let bfsIndex = 0;\n while (bfsIndex < bfsQueue.length) {\n const entry = bfsQueue[bfsIndex];\n\n const m = 'module' in entry ? entry.module : entry;\n\n m.imports?.forEach((depEntry) => {\n if (!bfsVisits.has(depEntry)) {\n bfsVisits.add(depEntry);\n bfsQueue.push(depEntry);\n }\n });\n\n bfsIndex++;\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.beforeBinding?.(container);\n }\n\n for (let i = 0; i < bfsQueue.length; i++) {\n const entry = bfsQueue[i];\n bindModuleEntry(container, entry);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n m.afterBinding?.(container);\n }\n\n for (let i = bfsQueue.length - 1; i >= 0; i--) {\n const entry = bfsQueue[i];\n const m = 'module' in entry ? entry.module : entry;\n if (m.strategy === 'eager') {\n container.resolve(m.token);\n }\n }\n}\n\nfunction bindModuleEntry(\n container: Container,\n entry: ModuleBindingEntry,\n): void {\n let module: ModuleDeclaration<AnyObject>;\n let options: BindModuleOptions | undefined;\n\n if ('module' in entry) {\n module = entry.module;\n options = entry.options;\n } else {\n module = entry;\n }\n\n const scope = options?.scope;\n const exportedValueTokens = new Set<Token<unknown>>();\n const moduleExports = module.exports;\n\n if (moduleExports) {\n const keys = Object.keys(moduleExports);\n\n keys.forEach((valueKey) => {\n const valueToken = moduleExports[valueKey];\n if (valueToken) {\n exportedValueTokens.add(valueToken);\n\n container.bindFactory(\n valueToken,\n injectable((module) => module[valueKey], module.token),\n { scope },\n );\n }\n });\n }\n\n container.bindFactory(module.token, module.factory, {\n scope,\n onRemoved: (moduleInstance) => {\n if (moduleInstance.destroy) {\n moduleInstance.destroy();\n }\n\n exportedValueTokens.forEach((valueToken) => container.remove(valueToken));\n exportedValueTokens.clear();\n },\n });\n}\n\n/**\n * Binds dependency modules to the container\n *\n * @param container - Dependency container for binding\n * @param modules - Array of module binding entries: module declaration or `{module: ModuleDeclaration, options: BindModuleOptions}` objects.\n */\nexport function bindModules(\n container: Container,\n modules: ReadonlyArray<ModuleBindingEntry>,\n): void {\n modules.forEach((entry) => {\n if ('module' in entry) {\n bindModule(container, entry.module, entry.options);\n } else {\n bindModule(container, entry);\n }\n });\n}\n\n/**\n * Declares a module binding\n *\n * @param declaration - a module declaration\n * @param declaration.token - optional field\n *\n * @example\n * ```ts\n * const LOGGER_MODULE = declareModule<LoggerModule>({\n * factory: (container) => {\n * const transport = container.resolve(TRANSPORT_TOKEN).open();\n * return {\n * logger: { log: (message) => transport.write(message) },\n * destroy: () => transport.close(),\n * }\n * },\n * exports: {\n * logger: LOGGER_TOKEN,\n * },\n * });\n * ```\n */\nexport function declareModule<T extends Module<AnyObject>>(\n declaration: Omit<ModuleDeclaration<T>, 'token'> &\n Partial<Pick<ModuleDeclaration<T>, 'token'>>,\n): ModuleDeclaration<T> {\n return { ...declaration, token: declaration.token ?? token() };\n}\n\n/**\n * @deprecated Use `declareModule` instead\n *\n * Declares bindings of several modules\n *\n * @param modules - module declaration entries\n */\nexport function declareModuleBindings(\n modules: ReadonlyArray<ModuleBindingEntry>,\n): ModuleDeclaration<Module> {\n return declareModule({\n factory: () => ({}),\n imports: modules,\n });\n}\n"],"mappings":";;;;;;;;CAwDA,SAAgB,MAAS,SAA2C;EAClE,MAAM,aACJ,OAAO,YAAY,WAAW,EAAE,aAAa,SAAS,GAAG;AAM3D,SAAO,EAAE,iEAJc,WAAY,OAC/B,OAAO,IAAI,WAAW,IAAI,GAC1B,+DAAO,WAAY,YAAY,EAElB;;CAcnB,SAAgB,SACd,OACA,eAC8B;AAC9B,SAAO;GACL,QAAQ,MAAM;GACd,YAAY;GACZ;GACD;;;;;;;;CCjFH,IAAa,gBAAb,cAAmC,MAAM;EACvC,YAAY,SAAiB;AAC3B,SAAM,QAAQ;AACd,UAAO,eAAe,MAAM,IAAI,OAAO,UAAU;AACjD,QAAK,OAAO;;;;CAiFhB,MAAa,YAA8B,MAAM,kBAAkB;;CAEnE,MAAa,oBAAqD,MAChE,wBACD;;CAED,MAAa,WAA4B,MAAM,iBAAiB;;CAGhE,MAAM,YAAY,QAAQ;;CAG1B,MAAM,gBAA8B;;CAepC,MAAa,gBAAqC,MAAM,qBAAqB;;CAM7E,SAAS,SAAY,SAA2C;;AAC9D,6EAAO,QAAS,gEAAS;;;CAI3B,SAAS,aAAgB,SAA4B;AACnD,SAAO,QAAQ,UAAU,UACvB,QAAQ,UAAU,YAClB,QAAQ,UAAU,cAChB,QAAQ,YACR;;;CAIN,SAAS,gBAAmB,OAA0B;AACpD,SACE,MAAM,WAAW,UAAU,UAC3B,MAAM,WAAW,kBAAkB,UACnC,MAAM,WAAW,SAAS;;;;;;;;;CAW9B,SAAgB,gBACd,WACW;EACX,MAAM,UAAwD,YAC1D,MAAM,QAAQ,UAAU,GACtB,CAAC,GAAG,UAAU,GACd,CAAC,UAAU,GACb;EAEJ,MAAM,yBAAoB,IAAI,KAAkB;EAChD,MAAM,4BAA0B,IAAI,KAAkC;EAEtE,MAAM,YAAuB;GAC3B,UAAa,OAAiB,OAAgB;AAC5C,QAAI,gBAAgB,MAAM,CACxB;AAGF,WAAO,IAAI,MAAM,QAAQ,MAAM;;GAGjC,YACE,OACA,SACA,SACM;AACN,QAAI,gBAAgB,MAAM,CACxB;AAGF,cAAU,IAAI,MAAM,QAAQ;KAAE;KAAS;KAAS,CAAC;;GAGnD,OAAU,OAAuB;;AAC/B,QAAI,gBAAgB,MAAM,CACxB;IAGF,MAAM,4BAAU,UAAU,IAAI,MAAM,OAAO,kEAAE;AAC7C,QAAI,QACF,kBAAiB,MAAM,QAAQ,QAAQ;AAGzC,WAAO,OAAO,MAAM,OAAO;AAC3B,cAAU,OAAO,MAAM,OAAO;;GAGhC,YAAkB;IAEhB,IAAI,WAAW;IACf,IAAI;AACJ,QAAI;AACF,eAAU,SAAS,SAAS,gBAAgB;AAC1C,UAAI;AACF,WAAI,QAAQ,QACV,kBAAiB,aAAa,QAAQ,QAAQ;eAEzC,OAAO;AACd,WAAI,CAAC,UAAU;AACb,mBAAW;AACX,qBAAa;;;OAGjB;cACM;AACR,YAAO,OAAO;AACd,eAAU,OAAO;AACjB,yBAAoB;;AAEtB,QAAI,SAAU,OAAM;;GAGtB,SAAS,OAAgC;AACvC,WACE,OAAO,IAAI,MAAM,OAAO,IACxB,UAAU,IAAI,MAAM,OAAO,IAC3B,0BAA0B,SAAS,MAAM;;GAI7C,IAAO,OAAgC;IACrC,MAAM,QAAQ,SAAS,OAAO,UAAU;AACxC,QAAI,UAAU,UACZ,QAAO;AAGT,QAAI,MAAM,WACR,QAAO,MAAM;;GAMjB,QAAW,OAAoB;;IAC7B,MAAM,QAAQ,SAAS,OAAO,UAAU;AACxC,QAAI,UAAU,UACZ,QAAO;AAGT,QAAI,MAAM,WACR,QAAO,MAAM;AAGf,UAAM,IAAI,cACR,mCAAU,MAAM,OAAO,oFAAe,GAAG,mBAC1C;;GAEJ;EAED,SAAS,SACP,OACA,QACsB;GACtB,MAAM,QAAQ,OAAO,IAAI,MAAM,OAAO;GACtC,MAAM,WAAW,UAAU,UAAa,OAAO,IAAI,MAAM,OAAO;AAEhE,OAAI,YAAY,WAAW,UACzB,QAAO;GAGT,MAAM,iBAAiB,UAAU,IAAI,MAAM,OAAO;AAClD,OAAI,eAGF,SAFc,SAAS,eAAe,QAAQ,EAE9C;IACE,KAAK,YACH,KAAI,SACF,QAAO;aACE,0BAA0B,SAAS,MAAM,CAClD;SACK;KAEL,MAAM,QAAQ,eAAe,QAAQ,UAAU;AAC/C,eAAU,UAAU,OAAO,MAAM;AACjC,YAAO;;IAIX,KAAK,SACH,KAAI,SACF,QAAO;SACF;KAEL,MAAM,QAAQ,eAAe,QAAQ,UAAU;AAC/C,eAAU,UAAU,OAAO,MAAM;AACjC,YAAO;;IAIX,KAAK,YAEH,QAAO,eAAe,QAAQ,OAAO;;AAK3C,OAAI,SACF,QAAO;AAGT,OAAI,QACF,QAAO,6BAA6B,SAAS,OAAO,OAAO;AAG7D,UAAO;;EAGT,SAAS,iBACP,aACA,SACA;GACA,MAAM,YAAY,aAAa,QAAQ;AACvC,OAAI,WAAW;IACb,MAAM,QAAQ,OAAO,IAAI,YAAY;AACrC,QAAI,UAAU,UAAa,OAAO,IAAI,YAAY,CAChD,WAAU,MAAM;;;EAKtB,SAAS,qBAAqB;AAC5B,UAAO,IAAI,UAAU,QAAQ,UAAU;AACvC,UAAO,IAAI,SAAS,QAAQ,SAAS;AACrC,UAAO,IAAI,cAAc,QAAQ,UAAU;AAE3C,OAAI,QACF,QAAO,IAAI,kBAAkB,QAAQ,QAAQ;;AAIjD,sBAAoB;AACpB,SAAO;;CAGT,SAAS,0BACP,SACA,OACS;AACT,MAAI,CAAC,QAAS,QAAO;AAErB,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAClC,KAAI,QAAQ,GAAG,SAAS,MAAM,CAC5B,QAAO;AAIX,SAAO;;CAGT,SAAS,6BACP,SACA,OACA,QACsB;AACtB,OAAK,MAAM,mBAAmB,SAAS;GACrC,MAAM,iBAAiB,gBAAgB,IAAI,SAAS;AACpD,OAAI,gBAAgB;IAClB,MAAM,WAAW,eAAe,OAAO,OAAO;AAC9C,QAAI,aAAa,UACf,QAAO;;;AAKb,SAAO;;;;;;;;CC7WT,SAAgB,QAAW,OAAmC;AAC5D,SACE,UAAU,UACV,UAAU,QACV,OAAO,UAAU,YACjB,YAAY,SACZ,OAAQ,MAAc,WAAW;;;;;;;;CAUrC,SAAgB,eACd,WACA,OACA,OACM;;EAEN,MAAM,aAAa,CAAC,qBADD,UAAU,IAAI,MAAM,2DAAI,EAAE,EACV,MAAM;AACzC,YAAU,UAAU,OAAO,WAAW;;;;;;;;;;;;;;;;;;;CAoBxC,SAAgB,gBAOd,WAAsB,OAAuB;AAC7C,MAAI,QAAQ,MAAM,CAChB,QAAO,UAAU,IAAI,MAAM;EAG7B,MAAM,MAAW,EAAE;AACnB,SAAO,KAAK,MAAM,CAAC,SAAS,QAAS,IAAI,OAAO,UAAU,IAAI,MAAM,KAAK,CAAE;AAC3E,SAAO;;;;;;;;;;;;;;;;;;;CAoBT,SAAgB,iBASd,WAAsB,GAAG,QAAwB;AACjD,SAAO,OAAO,KAAK,SAAS,gBAAgB,WAAW,KAAK,CAAC;;;;;;;;;;;;;;;;;;;CAoB/D,SAAgB,aAOd,WAAsB,OAAuB;AAC7C,MAAI,QAAQ,MAAM,CAChB,QAAO,UAAU,QAAQ,MAAM;EAGjC,MAAM,MAAW,EAAE;AACnB,SAAO,KAAK,MAAM,CAAC,SAChB,QAAS,IAAI,OAAO,UAAU,QAAQ,MAAM,KAAK,CACnD;AACD,SAAO;;;;;;;;;;;;;;;;;;;CAoBT,SAAgB,cASd,WAAsB,GAAG,QAAwB;AACjD,SAAO,OAAO,KAAK,SAAS,aAAa,WAAW,KAAK,CAAC;;;;;;;;;;;;;CAc5D,SAAgB,WAYd,SACA,GAAG,QAC+B;AAClC,UAAQ,cAAc;GACpB,MAAM,SAAiB,cAAc,WAAW,GAAG,OAAO;AAC1D,UAAO,QAAQ,MAAM,MAAM,OAAO;;;;;;;;;;;;;;;CAgBtC,SAAgB,gBAYd,aACA,GAAG,QAC+B;AAClC,SAAO,YACJ,GAAG,WAAW,IAAI,YAAY,GAAG,OAAO,EACzC,GAAG,OACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CCxHH,SAAgB,WACd,WACA,mBACA,SACM;EACN,MAAM,YAAgC;GACpC,QAAQ;GACR,SAAS,mDAAW,EAAE;GACvB;EAED,MAAM,YAAY,IAAI,IAAwB,CAAC,UAAU,CAAC;EAC1D,MAAM,WAAiC,CAAC,UAAU;EAElD,IAAI,WAAW;AACf,SAAO,WAAW,SAAS,QAAQ;;GACjC,MAAM,QAAQ,SAAS;AAIvB,kBAFU,YAAY,QAAQ,MAAM,SAAS,OAE3C,yDAAS,SAAS,aAAa;AAC/B,QAAI,CAAC,UAAU,IAAI,SAAS,EAAE;AAC5B,eAAU,IAAI,SAAS;AACvB,cAAS,KAAK,SAAS;;KAEzB;AAEF;;AAGF,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;;GACxC,MAAM,QAAQ,SAAS;GACvB,MAAM,IAAI,YAAY,QAAQ,MAAM,SAAS;AAC7C,yBAAE,mFAAgB,UAAU;;AAG9B,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,MAAM,QAAQ,SAAS;AACvB,mBAAgB,WAAW,MAAM;;AAGnC,OAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;;GAC7C,MAAM,QAAQ,SAAS;GACvB,MAAM,IAAI,YAAY,QAAQ,MAAM,SAAS;AAC7C,wBAAE,gFAAe,UAAU;;AAG7B,OAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;GAC7C,MAAM,QAAQ,SAAS;GACvB,MAAM,IAAI,YAAY,QAAQ,MAAM,SAAS;AAC7C,OAAI,EAAE,aAAa,QACjB,WAAU,QAAQ,EAAE,MAAM;;;CAKhC,SAAS,gBACP,WACA,OACM;EACN,IAAI;EACJ,IAAI;AAEJ,MAAI,YAAY,OAAO;AACrB,YAAS,MAAM;AACf,aAAU,MAAM;QAEhB,UAAS;EAGX,MAAM,0DAAQ,QAAS;EACvB,MAAM,sCAAsB,IAAI,KAAqB;EACrD,MAAM,gBAAgB,OAAO;AAE7B,MAAI,cAGF,CAFa,OAAO,KAAK,cAAc,CAElC,SAAS,aAAa;GACzB,MAAM,aAAa,cAAc;AACjC,OAAI,YAAY;AACd,wBAAoB,IAAI,WAAW;AAEnC,cAAU,YACR,YACA,YAAY,WAAW,OAAO,WAAW,OAAO,MAAM,EACtD,EAAE,OAAO,CACV;;IAEH;AAGJ,YAAU,YAAY,OAAO,OAAO,OAAO,SAAS;GAClD;GACA,YAAY,mBAAmB;AAC7B,QAAI,eAAe,QACjB,gBAAe,SAAS;AAG1B,wBAAoB,SAAS,eAAe,UAAU,OAAO,WAAW,CAAC;AACzE,wBAAoB,OAAO;;GAE9B,CAAC;;;;;;;;CASJ,SAAgB,YACd,WACA,SACM;AACN,UAAQ,SAAS,UAAU;AACzB,OAAI,YAAY,MACd,YAAW,WAAW,MAAM,QAAQ,MAAM,QAAQ;OAElD,YAAW,WAAW,MAAM;IAE9B;;;;;;;;;;;;;;;;;;;;;;;;CAyBJ,SAAgB,cACd,aAEsB;;AACtB,2CAAY,oBAAa,6BAAO,YAAY,wEAAS,OAAO;;;;;;;;;CAU9D,SAAgB,sBACd,SAC2B;AAC3B,SAAO,cAAc;GACnB,gBAAgB,EAAE;GAClB,SAAS;GACV,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ditox",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "Dependency injection for web applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Mikhail Nasyrov <mikhail@mnasyrov.com> (https://github.com/mnasyrov)",
|
|
@@ -53,6 +53,5 @@
|
|
|
53
53
|
"clean": "rm -rf dist lib",
|
|
54
54
|
"build": "tsdown",
|
|
55
55
|
"typedoc": "typedoc"
|
|
56
|
-
}
|
|
57
|
-
"gitHead": "b092f487c06b35fb2c1125303d6e4942bc2a66b9"
|
|
56
|
+
}
|
|
58
57
|
}
|
package/src/container.test.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { describe, expect, it, test, vi } from 'vitest';
|
|
|
2
2
|
import {
|
|
3
3
|
CONTAINER,
|
|
4
4
|
createContainer,
|
|
5
|
+
FACTORIES_MAP,
|
|
5
6
|
PARENT_CONTAINERS,
|
|
7
|
+
RESOLVER,
|
|
6
8
|
ResolverError,
|
|
7
9
|
} from './container';
|
|
8
10
|
import { optional, token } from './tokens';
|
|
@@ -366,6 +368,32 @@ describe('Container', () => {
|
|
|
366
368
|
});
|
|
367
369
|
|
|
368
370
|
describe('removeAll()', () => {
|
|
371
|
+
it('should restore internal tokens after a callback throws and allow reuse', () => {
|
|
372
|
+
const parent = createContainer();
|
|
373
|
+
const container = createContainer(parent);
|
|
374
|
+
const child = createContainer(container);
|
|
375
|
+
parent.bindValue(STRING, 'parent');
|
|
376
|
+
container.bindFactory(NUMBER, () => 1, {
|
|
377
|
+
onRemoved() {
|
|
378
|
+
throw new Error('cleanup failed');
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
container.resolve(NUMBER);
|
|
382
|
+
|
|
383
|
+
expect(() => container.removeAll()).toThrow('cleanup failed');
|
|
384
|
+
expect(container.resolve(CONTAINER)).toBe(container);
|
|
385
|
+
expect(container.resolve(PARENT_CONTAINERS)).toEqual([parent]);
|
|
386
|
+
expect(container.resolve(RESOLVER)).toBeTypeOf('function');
|
|
387
|
+
expect(container.hasToken(FACTORIES_MAP)).toBe(true);
|
|
388
|
+
expect(child.resolve(STRING)).toBe('parent');
|
|
389
|
+
const onRemoved = vi.fn();
|
|
390
|
+
container.bindFactory(NUMBER, () => 2, { onRemoved });
|
|
391
|
+
expect(child.resolve(NUMBER)).toBe(2);
|
|
392
|
+
container.removeAll();
|
|
393
|
+
expect(onRemoved).toHaveBeenCalledExactlyOnceWith(2);
|
|
394
|
+
expect(child.get(NUMBER)).toBeUndefined();
|
|
395
|
+
});
|
|
396
|
+
|
|
369
397
|
it('should remove all values and factories', () => {
|
|
370
398
|
const container = createContainer();
|
|
371
399
|
|
|
@@ -688,5 +716,20 @@ describe('Container', () => {
|
|
|
688
716
|
|
|
689
717
|
expect(() => child.resolve(NUMBER)).toThrow(ResolverError);
|
|
690
718
|
});
|
|
719
|
+
|
|
720
|
+
it('should NOT resolve a token from a parent that does NOT provide a RESOLVER token', () => {
|
|
721
|
+
const T1 = token<number>('T1');
|
|
722
|
+
|
|
723
|
+
const customParent = {
|
|
724
|
+
hasToken: () => true,
|
|
725
|
+
get: (t: any) => (t === RESOLVER ? undefined : undefined),
|
|
726
|
+
resolve: () => {
|
|
727
|
+
throw new Error('Not implemented');
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const container = createContainer(customParent);
|
|
732
|
+
expect(container.get(T1)).toBeUndefined();
|
|
733
|
+
});
|
|
691
734
|
});
|
|
692
735
|
});
|
package/src/container.ts
CHANGED
|
@@ -75,6 +75,8 @@ export type Container = {
|
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* Removes all bindings in the container.
|
|
78
|
+
* Continues after cleanup failures and rethrows the first thrown value unchanged
|
|
79
|
+
* after clearing bindings and restoring internal tokens. Later failures are discarded.
|
|
78
80
|
*/
|
|
79
81
|
removeAll(): void;
|
|
80
82
|
};
|
|
@@ -196,15 +198,28 @@ export function createContainer(
|
|
|
196
198
|
},
|
|
197
199
|
|
|
198
200
|
removeAll(): void {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
// A separate flag preserves thrown values such as undefined or false.
|
|
202
|
+
let hasError = false;
|
|
203
|
+
let firstError: unknown;
|
|
204
|
+
try {
|
|
205
|
+
factories.forEach((context, tokenSymbol) => {
|
|
206
|
+
try {
|
|
207
|
+
if (context.options) {
|
|
208
|
+
executeOnRemoved(tokenSymbol, context.options);
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (!hasError) {
|
|
212
|
+
hasError = true;
|
|
213
|
+
firstError = error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
} finally {
|
|
218
|
+
values.clear();
|
|
219
|
+
factories.clear();
|
|
220
|
+
bindInternalTokens();
|
|
221
|
+
}
|
|
222
|
+
if (hasError) throw firstError;
|
|
208
223
|
},
|
|
209
224
|
|
|
210
225
|
hasToken(token: Token<unknown>): boolean {
|
package/src/modules.test.ts
CHANGED
|
@@ -521,4 +521,22 @@ describe('declareModuleBindings()', () => {
|
|
|
521
521
|
|
|
522
522
|
expect(container.get(MODULE_BINDINGS.token)).toEqual({});
|
|
523
523
|
});
|
|
524
|
+
|
|
525
|
+
it('should ignore falsy tokens in exports', () => {
|
|
526
|
+
const container = createContainer();
|
|
527
|
+
|
|
528
|
+
const MODULE = declareModule({
|
|
529
|
+
factory: () => ({
|
|
530
|
+
valid: 1,
|
|
531
|
+
invalid: 2,
|
|
532
|
+
}),
|
|
533
|
+
exports: {
|
|
534
|
+
valid: token(),
|
|
535
|
+
invalid: undefined,
|
|
536
|
+
} as any,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
bindModule(container, MODULE);
|
|
540
|
+
expect(container.get(MODULE.token)).toEqual({ valid: 1, invalid: 2 });
|
|
541
|
+
});
|
|
524
542
|
});
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { bindModule, createContainer, declareModule, token } from './index';
|
|
3
|
+
|
|
4
|
+
describe('3.x removal compatibility', () => {
|
|
5
|
+
it('retains a single binding after its callback throws and allows retry', () => {
|
|
6
|
+
const container = createContainer();
|
|
7
|
+
const resource = token();
|
|
8
|
+
const value = {};
|
|
9
|
+
const failure = new Error('failure');
|
|
10
|
+
const onRemoved = vi.fn().mockImplementationOnce(() => {
|
|
11
|
+
throw failure;
|
|
12
|
+
});
|
|
13
|
+
container.bindFactory(resource, () => value, { onRemoved });
|
|
14
|
+
container.resolve(resource);
|
|
15
|
+
|
|
16
|
+
expect(() => container.remove(resource)).toThrow(failure);
|
|
17
|
+
expect(container.resolve(resource)).toBe(value);
|
|
18
|
+
container.remove(resource);
|
|
19
|
+
expect(container.hasToken(resource)).toBe(false);
|
|
20
|
+
expect(onRemoved).toHaveBeenCalledTimes(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('retains a module and its exports after a single removal fails', () => {
|
|
24
|
+
const container = createContainer();
|
|
25
|
+
const exported = token<number>();
|
|
26
|
+
const failure = new Error('destroy failed');
|
|
27
|
+
const destroy = vi.fn().mockImplementationOnce(() => {
|
|
28
|
+
throw failure;
|
|
29
|
+
});
|
|
30
|
+
const module = declareModule({
|
|
31
|
+
factory: () => ({ value: 42, destroy }),
|
|
32
|
+
exports: { value: exported },
|
|
33
|
+
});
|
|
34
|
+
bindModule(container, module);
|
|
35
|
+
expect(container.resolve(exported)).toBe(42);
|
|
36
|
+
expect(() => container.remove(module.token)).toThrow(failure);
|
|
37
|
+
expect(container.hasToken(module.token)).toBe(true);
|
|
38
|
+
expect(container.resolve(exported)).toBe(42);
|
|
39
|
+
container.remove(module.token);
|
|
40
|
+
expect(container.hasToken(module.token)).toBe(false);
|
|
41
|
+
expect(container.hasToken(exported)).toBe(false);
|
|
42
|
+
expect(destroy).toHaveBeenCalledTimes(2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('keeps legacy removal of replacements made by a single callback', () => {
|
|
46
|
+
const container = createContainer();
|
|
47
|
+
const resource = token();
|
|
48
|
+
const replacementFactory = vi.fn(() => 2);
|
|
49
|
+
container.bindFactory(resource, () => 1, {
|
|
50
|
+
onRemoved() {
|
|
51
|
+
container.bindFactory(resource, replacementFactory);
|
|
52
|
+
container.bindValue(resource, 2);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
container.resolve(resource);
|
|
56
|
+
container.remove(resource);
|
|
57
|
+
expect(container.hasToken(resource)).toBe(false);
|
|
58
|
+
expect(replacementFactory).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('keeps live traversal when callbacks remove and append other bindings', () => {
|
|
62
|
+
const container = createContainer();
|
|
63
|
+
const first = token();
|
|
64
|
+
const second = token();
|
|
65
|
+
const appended = token();
|
|
66
|
+
const calls: string[] = [];
|
|
67
|
+
container.bindFactory(first, () => 1, {
|
|
68
|
+
onRemoved() {
|
|
69
|
+
calls.push('first');
|
|
70
|
+
container.remove(second);
|
|
71
|
+
container.bindFactory(appended, () => 3, {
|
|
72
|
+
onRemoved: () => {
|
|
73
|
+
calls.push('appended');
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
container.resolve(appended);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
container.bindFactory(second, () => 2, {
|
|
80
|
+
onRemoved: () => {
|
|
81
|
+
calls.push('second');
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
container.resolve(first);
|
|
85
|
+
container.resolve(second);
|
|
86
|
+
container.removeAll();
|
|
87
|
+
expect(calls).toEqual(['first', 'second', 'appended']);
|
|
88
|
+
[first, second, appended].forEach((resource) =>
|
|
89
|
+
expect(container.hasToken(resource)).toBe(false),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('keeps bounded nested bulk cleanup active rather than making it a no-op', () => {
|
|
94
|
+
const container = createContainer();
|
|
95
|
+
const first = token();
|
|
96
|
+
const second = token();
|
|
97
|
+
const calls: string[] = [];
|
|
98
|
+
let nested = false;
|
|
99
|
+
container.bindFactory(first, () => 1, {
|
|
100
|
+
onRemoved() {
|
|
101
|
+
calls.push('first');
|
|
102
|
+
if (!nested) {
|
|
103
|
+
nested = true;
|
|
104
|
+
container.removeAll();
|
|
105
|
+
expect(container.hasToken(second)).toBe(false);
|
|
106
|
+
calls.push('returned');
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
container.bindFactory(second, () => 2, {
|
|
111
|
+
onRemoved: () => {
|
|
112
|
+
calls.push('second');
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
container.resolve(first);
|
|
116
|
+
container.resolve(second);
|
|
117
|
+
container.removeAll();
|
|
118
|
+
expect(calls).toEqual(['first', 'first', 'second', 'returned']);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('visits a factory deleted and reinserted at an already visited key', () => {
|
|
122
|
+
const container = createContainer();
|
|
123
|
+
const first = token<number>();
|
|
124
|
+
const second = token();
|
|
125
|
+
const calls: number[] = [];
|
|
126
|
+
const onRemoved = (value: number) => {
|
|
127
|
+
calls.push(value);
|
|
128
|
+
};
|
|
129
|
+
container.bindFactory(first, () => 1, { onRemoved });
|
|
130
|
+
container.bindFactory(second, () => 2, {
|
|
131
|
+
onRemoved() {
|
|
132
|
+
container.remove(first);
|
|
133
|
+
container.bindFactory(first, () => 3, { onRemoved });
|
|
134
|
+
container.resolve(first);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
container.resolve(first);
|
|
138
|
+
container.resolve(second);
|
|
139
|
+
container.removeAll();
|
|
140
|
+
expect(calls).toEqual([1, 1, 3]);
|
|
141
|
+
expect(container.hasToken(first)).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('keeps bounded single-token reentry active', () => {
|
|
145
|
+
const container = createContainer();
|
|
146
|
+
const resource = token();
|
|
147
|
+
let calls = 0;
|
|
148
|
+
container.bindFactory(resource, () => 1, {
|
|
149
|
+
onRemoved() {
|
|
150
|
+
calls++;
|
|
151
|
+
if (calls === 1) container.remove(resource);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
container.resolve(resource);
|
|
155
|
+
container.removeAll();
|
|
156
|
+
expect(calls).toBe(2);
|
|
157
|
+
expect(container.hasToken(resource)).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('does not revisit replacements at already visited keys', () => {
|
|
161
|
+
const container = createContainer();
|
|
162
|
+
const first = token();
|
|
163
|
+
const second = token();
|
|
164
|
+
const onRemoved = vi.fn();
|
|
165
|
+
container.bindFactory(first, () => 1, { onRemoved });
|
|
166
|
+
container.bindFactory(second, () => 2, {
|
|
167
|
+
onRemoved() {
|
|
168
|
+
container.bindValue(first, 3);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
container.resolve(first);
|
|
172
|
+
container.resolve(second);
|
|
173
|
+
container.removeAll();
|
|
174
|
+
expect(onRemoved).toHaveBeenCalledExactlyOnceWith(1);
|
|
175
|
+
expect(container.hasToken(first)).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('bulk cleanup failures with reentry and accessors', () => {
|
|
180
|
+
it('continues after a nested single removal throws, retaining its legacy retry', () => {
|
|
181
|
+
const container = createContainer();
|
|
182
|
+
const first = token();
|
|
183
|
+
const second = token();
|
|
184
|
+
const last = token();
|
|
185
|
+
const failure = new Error('nested failure');
|
|
186
|
+
const onRemoved = vi.fn(() => {
|
|
187
|
+
throw failure;
|
|
188
|
+
});
|
|
189
|
+
const lastCallback = vi.fn();
|
|
190
|
+
container.bindFactory(first, () => 1, {
|
|
191
|
+
onRemoved: () => container.remove(second),
|
|
192
|
+
});
|
|
193
|
+
container.bindFactory(second, () => 2, { onRemoved });
|
|
194
|
+
container.bindFactory(last, () => 3, { onRemoved: lastCallback });
|
|
195
|
+
[first, second, last].forEach((resource) => container.resolve(resource));
|
|
196
|
+
expect(() => container.removeAll()).toThrow(failure);
|
|
197
|
+
expect(onRemoved).toHaveBeenCalledTimes(2);
|
|
198
|
+
expect(lastCallback).toHaveBeenCalledTimes(1);
|
|
199
|
+
[first, second, last].forEach((resource) =>
|
|
200
|
+
expect(container.hasToken(resource)).toBe(false),
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('finishes nested bulk cleanup before propagating its first error', () => {
|
|
205
|
+
const container = createContainer();
|
|
206
|
+
const first = token();
|
|
207
|
+
const second = token();
|
|
208
|
+
const third = token();
|
|
209
|
+
let nested = false;
|
|
210
|
+
const failure = new Error('nested failure');
|
|
211
|
+
const onRemoved = vi.fn();
|
|
212
|
+
container.bindFactory(first, () => 1, {
|
|
213
|
+
onRemoved() {
|
|
214
|
+
if (!nested) {
|
|
215
|
+
nested = true;
|
|
216
|
+
container.removeAll();
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
container.bindFactory(second, () => 2, {
|
|
221
|
+
onRemoved() {
|
|
222
|
+
throw failure;
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
container.bindFactory(third, () => 3, { onRemoved });
|
|
226
|
+
[first, second, third].forEach((resource) => container.resolve(resource));
|
|
227
|
+
expect(() => container.removeAll()).toThrow(failure);
|
|
228
|
+
expect(onRemoved).toHaveBeenCalledTimes(1);
|
|
229
|
+
[first, second, third].forEach((resource) =>
|
|
230
|
+
expect(container.hasToken(resource)).toBe(false),
|
|
231
|
+
);
|
|
232
|
+
container.removeAll();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('continues after an onRemoved accessor throws even for an unresolved factory', () => {
|
|
236
|
+
const container = createContainer();
|
|
237
|
+
const first = token();
|
|
238
|
+
const second = token();
|
|
239
|
+
const failure = new Error('accessor failed');
|
|
240
|
+
const factory = vi.fn();
|
|
241
|
+
const getter = vi.fn(() => {
|
|
242
|
+
throw failure;
|
|
243
|
+
});
|
|
244
|
+
const onRemoved = vi.fn();
|
|
245
|
+
container.bindFactory(first, factory, {
|
|
246
|
+
get onRemoved() {
|
|
247
|
+
return getter();
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
container.bindFactory(second, () => undefined, { onRemoved });
|
|
251
|
+
container.resolve(second);
|
|
252
|
+
expect(() => container.removeAll()).toThrow(failure);
|
|
253
|
+
expect(getter).toHaveBeenCalledTimes(1);
|
|
254
|
+
expect(factory).not.toHaveBeenCalled();
|
|
255
|
+
expect(onRemoved).toHaveBeenCalledExactlyOnceWith(undefined);
|
|
256
|
+
expect(container.hasToken(first)).toBe(false);
|
|
257
|
+
expect(container.hasToken(second)).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { bindModule, createContainer, declareModule, token } from './index';
|
|
3
|
+
|
|
4
|
+
describe('container teardown', () => {
|
|
5
|
+
it('finishes cleanup after an onRemoved failure and does not retry callbacks', () => {
|
|
6
|
+
const container = createContainer();
|
|
7
|
+
const failure = new Error('cleanup failed');
|
|
8
|
+
const calls: string[] = [];
|
|
9
|
+
const tokens = ['before', 'failure', 'after'].map((name) => {
|
|
10
|
+
const resource = token(name);
|
|
11
|
+
container.bindFactory(resource, () => ({}), {
|
|
12
|
+
onRemoved() {
|
|
13
|
+
calls.push(name);
|
|
14
|
+
if (name === 'failure') throw failure;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
container.resolve(resource);
|
|
18
|
+
return resource;
|
|
19
|
+
});
|
|
20
|
+
const value = token();
|
|
21
|
+
const unresolved = token();
|
|
22
|
+
const factory = vi.fn();
|
|
23
|
+
const onRemoved = vi.fn();
|
|
24
|
+
container.bindValue(value, 42);
|
|
25
|
+
container.bindFactory(unresolved, factory, { onRemoved });
|
|
26
|
+
|
|
27
|
+
expect(() => container.removeAll()).toThrow(failure);
|
|
28
|
+
expect(calls).toEqual(['before', 'failure', 'after']);
|
|
29
|
+
for (const resource of [...tokens, value, unresolved]) {
|
|
30
|
+
expect(container.hasToken(resource)).toBe(false);
|
|
31
|
+
}
|
|
32
|
+
expect(factory).not.toHaveBeenCalled();
|
|
33
|
+
expect(onRemoved).not.toHaveBeenCalled();
|
|
34
|
+
expect(() => container.removeAll()).not.toThrow();
|
|
35
|
+
expect(calls).toEqual(['before', 'failure', 'after']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it.each([
|
|
39
|
+
new Error('first'),
|
|
40
|
+
undefined,
|
|
41
|
+
null,
|
|
42
|
+
false,
|
|
43
|
+
0,
|
|
44
|
+
'failure',
|
|
45
|
+
])('rethrows the first value unchanged after all callbacks, including %s', (failure) => {
|
|
46
|
+
const container = createContainer();
|
|
47
|
+
const callbacks = [failure, new Error('second')].map((error) => {
|
|
48
|
+
const resource = token();
|
|
49
|
+
const onRemoved = vi.fn(() => {
|
|
50
|
+
throw error;
|
|
51
|
+
});
|
|
52
|
+
container.bindFactory(resource, () => undefined, { onRemoved });
|
|
53
|
+
container.resolve(resource);
|
|
54
|
+
return onRemoved;
|
|
55
|
+
});
|
|
56
|
+
let threw = false;
|
|
57
|
+
try {
|
|
58
|
+
container.removeAll();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
threw = true;
|
|
61
|
+
expect(error).toBe(failure);
|
|
62
|
+
}
|
|
63
|
+
expect(threw).toBe(true);
|
|
64
|
+
callbacks.forEach((callback) => expect(callback).toHaveBeenCalledTimes(1));
|
|
65
|
+
container.removeAll();
|
|
66
|
+
callbacks.forEach((callback) => expect(callback).toHaveBeenCalledTimes(1));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it.each([
|
|
70
|
+
'singleton',
|
|
71
|
+
'scoped',
|
|
72
|
+
] as const)('only disposes locally cached %s values and leaves parent values intact', (scope) => {
|
|
73
|
+
const parent = createContainer();
|
|
74
|
+
const child = createContainer(parent);
|
|
75
|
+
const inherited = token();
|
|
76
|
+
const shared = token();
|
|
77
|
+
const owned = token();
|
|
78
|
+
const parentCallback = vi.fn();
|
|
79
|
+
const childCallback = vi.fn(() => {
|
|
80
|
+
throw new Error('child cleanup failed');
|
|
81
|
+
});
|
|
82
|
+
const inheritedFactory = vi.fn(() => ({}));
|
|
83
|
+
const sharedFactory = vi.fn(() => ({}));
|
|
84
|
+
parent.bindFactory(inherited, inheritedFactory, {
|
|
85
|
+
scope,
|
|
86
|
+
onRemoved: parentCallback,
|
|
87
|
+
});
|
|
88
|
+
parent.bindFactory(shared, sharedFactory, {
|
|
89
|
+
scope,
|
|
90
|
+
onRemoved: parentCallback,
|
|
91
|
+
});
|
|
92
|
+
child.bindFactory(shared, sharedFactory, {
|
|
93
|
+
scope,
|
|
94
|
+
onRemoved: childCallback,
|
|
95
|
+
});
|
|
96
|
+
child.bindFactory(owned, () => ({}), { scope, onRemoved: childCallback });
|
|
97
|
+
const inheritedValue = child.resolve(inherited);
|
|
98
|
+
const parentValue = parent.resolve(shared);
|
|
99
|
+
const childValue = child.resolve(shared);
|
|
100
|
+
child.resolve(owned);
|
|
101
|
+
|
|
102
|
+
expect(() => child.removeAll()).toThrow();
|
|
103
|
+
expect(childCallback).toHaveBeenCalledTimes(scope === 'scoped' ? 2 : 1);
|
|
104
|
+
if (scope === 'scoped') {
|
|
105
|
+
expect(childCallback).toHaveBeenCalledWith(childValue);
|
|
106
|
+
expect(childValue).not.toBe(parentValue);
|
|
107
|
+
} else {
|
|
108
|
+
expect(childValue).toBe(parentValue);
|
|
109
|
+
}
|
|
110
|
+
expect(parentCallback).not.toHaveBeenCalled();
|
|
111
|
+
expect(child.resolve(inherited)).toBe(inheritedValue);
|
|
112
|
+
expect(child.resolve(shared)).toBe(parentValue);
|
|
113
|
+
child.removeAll();
|
|
114
|
+
parent.removeAll();
|
|
115
|
+
expect(parentCallback).toHaveBeenCalledTimes(2);
|
|
116
|
+
expect(inheritedFactory).toHaveBeenCalledTimes(1);
|
|
117
|
+
expect(sharedFactory).toHaveBeenCalledTimes(scope === 'scoped' ? 2 : 1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('keeps resolved dependencies available during normal bulk callbacks', () => {
|
|
121
|
+
const container = createContainer();
|
|
122
|
+
const dependency = token();
|
|
123
|
+
const consumer = token();
|
|
124
|
+
const value = {};
|
|
125
|
+
const onRemoved = vi.fn();
|
|
126
|
+
container.bindFactory(dependency, () => value, { onRemoved });
|
|
127
|
+
container.bindFactory(consumer, () => ({}), {
|
|
128
|
+
onRemoved() {
|
|
129
|
+
expect(container.resolve(dependency)).toBe(value);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
container.resolve(dependency);
|
|
133
|
+
container.resolve(consumer);
|
|
134
|
+
container.removeAll();
|
|
135
|
+
expect(onRemoved).toHaveBeenCalledTimes(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('does not resolve unused factories or dispose transient values', () => {
|
|
139
|
+
const container = createContainer();
|
|
140
|
+
const unused = token();
|
|
141
|
+
const transient = token();
|
|
142
|
+
const unusedFactory = vi.fn();
|
|
143
|
+
const unusedCallback = vi.fn();
|
|
144
|
+
const transientFactory = vi.fn(() => ({}));
|
|
145
|
+
const transientCallback = vi.fn();
|
|
146
|
+
container.bindFactory(unused, unusedFactory, { onRemoved: unusedCallback });
|
|
147
|
+
// JavaScript callers can pass extra options; transient still has no teardown.
|
|
148
|
+
const options = {
|
|
149
|
+
scope: 'transient' as const,
|
|
150
|
+
onRemoved: transientCallback,
|
|
151
|
+
};
|
|
152
|
+
container.bindFactory(transient, transientFactory, options);
|
|
153
|
+
expect(container.resolve(transient)).not.toBe(container.resolve(transient));
|
|
154
|
+
|
|
155
|
+
container.removeAll();
|
|
156
|
+
expect(unusedFactory).not.toHaveBeenCalled();
|
|
157
|
+
expect(unusedCallback).not.toHaveBeenCalled();
|
|
158
|
+
expect(transientFactory).toHaveBeenCalledTimes(2);
|
|
159
|
+
expect(transientCallback).not.toHaveBeenCalled();
|
|
160
|
+
expect(container.hasToken(transient)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('cleans module exports and subsequent modules after destroy throws', () => {
|
|
164
|
+
const container = createContainer();
|
|
165
|
+
const exported = token<number>();
|
|
166
|
+
const failure = new Error('module cleanup failed');
|
|
167
|
+
const failingModule = declareModule({
|
|
168
|
+
factory: () => ({
|
|
169
|
+
value: 42,
|
|
170
|
+
destroy: () => {
|
|
171
|
+
throw failure;
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
exports: { value: exported },
|
|
175
|
+
});
|
|
176
|
+
const destroy = vi.fn();
|
|
177
|
+
const healthyModule = declareModule({ factory: () => ({ destroy }) });
|
|
178
|
+
bindModule(container, failingModule);
|
|
179
|
+
bindModule(container, healthyModule);
|
|
180
|
+
expect(container.resolve(exported)).toBe(42);
|
|
181
|
+
container.resolve(healthyModule.token);
|
|
182
|
+
|
|
183
|
+
expect(() => container.removeAll()).toThrow(failure);
|
|
184
|
+
expect(container.hasToken(exported)).toBe(false);
|
|
185
|
+
expect(container.hasToken(failingModule.token)).toBe(false);
|
|
186
|
+
expect(container.hasToken(healthyModule.token)).toBe(false);
|
|
187
|
+
container.removeAll();
|
|
188
|
+
expect(destroy).toHaveBeenCalledTimes(1);
|
|
189
|
+
});
|
|
190
|
+
});
|