@ogre-tools/injectable
Advanced tools
Comparing version 5.0.1 to 5.1.0
@@ -6,8 +6,47 @@ # Change Log | ||
### [5.0.1](https://github.com/ogre-works/ogre-tools/compare/v5.0.0...v5.0.1) (2022-02-16) | ||
## [5.1.0](https://github.com/ogre-works/ogre-tools/compare/v5.0.0...v5.1.0) (2022-03-11) | ||
### Features | ||
* Add "setup" to branch-tags of dependency graphing ([da315b2](https://github.com/ogre-works/ogre-tools/commit/da315b2dbcaa3e248b7133642db232e5e28bdc00)) | ||
* Add branch tags for dependency graphing ([9e43197](https://github.com/ogre-works/ogre-tools/commit/9e431972a7d0722b4bbffaacefb91e7d7cb9bb2c)) | ||
* Add injection context for withInjectables ([d69a57d](https://github.com/ogre-works/ogre-tools/commit/d69a57d7d1428342b76bb0d6ef272ede6c85858c)) | ||
* Expose instantiation parameters to error monitoring for better messages ([7b13267](https://github.com/ogre-works/ogre-tools/commit/7b13267fcb0798a1ac4879389747139ee348041d)) | ||
* **graphing:** Add appearance for sync/async ([a1523cf](https://github.com/ogre-works/ogre-tools/commit/a1523cf9ee3059adb52a6f8598d2fdcde6642055)) | ||
* **graphing:** Introduce graph customizers to eg. make reactive dependencies stick out ([8877020](https://github.com/ogre-works/ogre-tools/commit/8877020ab7e756d732f204f1b996b91e1343badc)) | ||
* **graphing:** Make graph more eye-ballable by adding colors and symbols ([d31bc57](https://github.com/ogre-works/ogre-tools/commit/d31bc57dcfc04677de0d4201475d9bdb763e1343)) | ||
* **graphing:** Make injection tokens stand out ([17796b5](https://github.com/ogre-works/ogre-tools/commit/17796b5d1608be6ddcd223dba456a79aa7908ae9)) | ||
* **graphing:** Make link and link info text color customizable separately ([aa42120](https://github.com/ogre-works/ogre-tools/commit/aa42120a70babd163dd817cd25583a090a7b3d18)) | ||
* **graphing:** Make link and link info text colors customizable ([2057001](https://github.com/ogre-works/ogre-tools/commit/205700110f2fbea33c90c9ff1f23848e0db6ccdc)) | ||
* Hide irrelevant data in dependency graphing ([7e38ac3](https://github.com/ogre-works/ogre-tools/commit/7e38ac34c219d6cba23c37e94decf2d04964d7df)) | ||
* **injectable:** Make setups able to inject many ([1dfca82](https://github.com/ogre-works/ogre-tools/commit/1dfca82428959204d352ba86292e2d418656fef2)) | ||
* Introduce ad-hoc injectables that do not require registration ([82c7a43](https://github.com/ogre-works/ogre-tools/commit/82c7a43ba530325c090334108827c92f700dfbd3)) | ||
* Introduce error monitoring for all injectables that return a function ([6f458f2](https://github.com/ogre-works/ogre-tools/commit/6f458f27fb028435f8d3147e78a9ba3dc4c98bd5)) | ||
* Introduce injectable extension for dependency graphing using Plant UML ([a32d206](https://github.com/ogre-works/ogre-tools/commit/a32d206a5d14feb7a61544ed47b5bdef9219e831)) | ||
* Introduce late registration ([5524f0e](https://github.com/ogre-works/ogre-tools/commit/5524f0e61a86ddbb60f0d33db5347fba90ba19b7)) | ||
* Introduce variation point for error handling of instantiation ([481b4d5](https://github.com/ogre-works/ogre-tools/commit/481b4d5a3a3995a5f2f0383e7986fa27ddaddf5c)) | ||
* Introduce variation point for global decoration of "instantiate" ([fe5e1a9](https://github.com/ogre-works/ogre-tools/commit/fe5e1a991884d589b60934e4dda1377b69202baa)) | ||
* Introduce variation point for targeted decoration of "instantiate" ([70b8918](https://github.com/ogre-works/ogre-tools/commit/70b8918f415c450638cade56098c1f8a4588b23c)) | ||
* Make error monitoring possible for TypeScript ([d0fd4ef](https://github.com/ogre-works/ogre-tools/commit/d0fd4ef7c4525189918397069dc2e6ff1de0eff0)) | ||
* Make injection tokens display more pretty in dependency graphing ([dc55d12](https://github.com/ogre-works/ogre-tools/commit/dc55d12a3f7d30ee957843e734968294d2e95efe)) | ||
* Make setuppables display more pretty in dependency graphing ([1fbdf74](https://github.com/ogre-works/ogre-tools/commit/1fbdf74405a7b7d97ec4680eed12149ad036d725)) | ||
* Permit instance key of any type ([0a68f24](https://github.com/ogre-works/ogre-tools/commit/0a68f24079b8d887e6b586bd1d2d83df7efc63e3)) | ||
* Show lifecycle names in dependency graphing ([9c0cf20](https://github.com/ogre-works/ogre-tools/commit/9c0cf205d7116d8438d307aee8b5752c37588851)) | ||
### Bug Fixes | ||
* Fix typing of getting injectable for injection token ([6412b3e](https://github.com/ogre-works/ogre-tools/commit/6412b3e083b6c3db02167813628d8d0e9062b82c)) | ||
* Add missing export for error monitoring in JS ([55fd4d2](https://github.com/ogre-works/ogre-tools/commit/55fd4d20241bd044be5668d80df4fdc493311da4)) | ||
* complete fix ([2bba4f3](https://github.com/ogre-works/ogre-tools/commit/2bba4f30cee996559a2135e718c87686f38f9bde)) | ||
* Define nodes before links in dependency graphing for human-readability ([af2c0d3](https://github.com/ogre-works/ogre-tools/commit/af2c0d3e4a67efebaa58eaf8c1d31bcfc5dd1f79)) | ||
* Faulty type parameter value ([17cfa76](https://github.com/ogre-works/ogre-tools/commit/17cfa76954a97671466cdcc7ae2c2433720ab5a8)) | ||
* Fix bad import ([16f06ce](https://github.com/ogre-works/ogre-tools/commit/16f06ce96b8e7b078e3e3d4df3ecc8fb266299e7)) | ||
* Fix typing of getting injectable for injection token ([53ca8ad](https://github.com/ogre-works/ogre-tools/commit/53ca8ad616d4c1ffa6fdde1811f2a404527bc585)) | ||
* lifecycleEnum.keyedSingleton not working ([06a86dc](https://github.com/ogre-works/ogre-tools/commit/06a86dc9fc19777616d1367300a8b13846ae7a0e)) | ||
* Make also injectMany comply to error monitoring for instantiation ([eb8baf7](https://github.com/ogre-works/ogre-tools/commit/eb8baf7b6fa0d71d7d5d9841b9e90093a5098799)) | ||
* Make setuppables display correctly in dependency graphing ([30425e4](https://github.com/ogre-works/ogre-tools/commit/30425e4de36b82ca97983390f54ff9e7d7b88ec7)) | ||
* Present dependency graph in correct order and with tokens ([17a5b6c](https://github.com/ogre-works/ogre-tools/commit/17a5b6c4654e9bc5ab0c5f0c499840d08beead13)) | ||
* Resolve PR comments ([bb2e1de](https://github.com/ogre-works/ogre-tools/commit/bb2e1debdcfe901f998eabcaba1941222e917950)) | ||
* **typings:** Improve typings to work with arbitrary injection params ([c1d900a](https://github.com/ogre-works/ogre-tools/commit/c1d900a22ae9d609e3b70b3d0a034dd5e81901b0)) | ||
@@ -14,0 +53,0 @@ |
23
index.js
@@ -6,2 +6,23 @@ import getInjectionToken from './src/getInjectionToken/getInjectionToken'; | ||
export { getInjectionToken, getInjectable, lifecycleEnum, createContainer }; | ||
import { | ||
registerDependencyGraphing, | ||
plantUmlDependencyGraphInjectable, | ||
dependencyGraphCustomizerToken, | ||
} from './src/dependency-injection-container/extensions/dependency-graphing/dependency-graphing'; | ||
import { | ||
registerErrorMonitoring, | ||
errorMonitorInjectionToken, | ||
} from './src/dependency-injection-container/extensions/error-monitoring/error-monitoring'; | ||
export { | ||
getInjectionToken, | ||
getInjectable, | ||
lifecycleEnum, | ||
createContainer, | ||
registerDependencyGraphing, | ||
plantUmlDependencyGraphInjectable, | ||
dependencyGraphCustomizerToken, | ||
registerErrorMonitoring, | ||
errorMonitorInjectionToken, | ||
}; |
/// <reference types="jest" /> | ||
declare module '@ogre-tools/injectable' { | ||
type InferFromInjectable<T> = T extends NormalInjectable< | ||
infer TInstance, | ||
infer TInstantiationParameter | ||
> | ||
? [TInstance, TInstantiationParameter] | ||
: never; | ||
type ValueType = | ||
| string | ||
| number | ||
| boolean | ||
| symbol | ||
| bigint | ||
| object | ||
| Array<any>; | ||
export type TentativeTuple<T> = T extends ValueType ? [T] : [undefined?]; | ||
export interface DiContainer extends DiContainerForInjection<false> { | ||
purge: (injectableKey: NormalInjectable<any, any>) => void; | ||
purge: (injectableKey: Injectable<any, any, any>) => void; | ||
runSetups: () => Promise<void>; | ||
override<TInjectable extends NormalInjectable<unknown, unknown>>( | ||
injectable: TInjectable, | ||
instantiateStub: ( | ||
di: DiContainerForInstantiate, | ||
...instantiationParameter: TentativeTuple< | ||
InferFromInjectable<TInjectable>[1] | ||
> | ||
) => InferFromInjectable<TInjectable>[0], | ||
override< | ||
InjectionInstance extends InjectionTokenInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam, | ||
>( | ||
injectable: Injectable< | ||
InjectionInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>, | ||
instantiateStub: Instantiate<InjectionInstance, InstantiationParam>, | ||
): void; | ||
register(injectable: NormalInjectable<any, any>): void; | ||
register(injectable: InjectionTokenInjectable<any>): void; | ||
register< | ||
InjectionInstance extends InjectionTokenInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam, | ||
>( | ||
injectable: Injectable< | ||
InjectionInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>, | ||
): void; | ||
preventSideEffects: () => void; | ||
} | ||
export interface DiContainerForSetup extends DiContainerForInjection<true> {} | ||
export type Instantiate<InjectionInstance, InstantiationParam> = { | ||
( | ||
di: DiContainerForInstantiate, | ||
param: InstantiationParam extends void ? void : InstantiationParam, | ||
): InjectionInstance; | ||
}; | ||
export interface DiContainerForInstantiate | ||
extends DiContainerForInjection<false> {} | ||
export type DiContainerForSetup = DiContainerForInjection<true>; | ||
export type DiContainerForInstantiate = DiContainerForInjection<false>; | ||
type InjectionToken<TInstance, TInstantiationParameter> = { | ||
template: TInstance; | ||
instantiationParameter: TInstantiationParameter; | ||
export interface InjectionToken<InjectionInstance, InstantiationParam> { | ||
template: InjectionInstance; | ||
instantiationParameter: InstantiationParam; | ||
key: Symbol; | ||
}; | ||
} | ||
interface CommonInjectable { | ||
export interface Injectable< | ||
InjectionInstance extends InjectionTokenInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam, | ||
> { | ||
id: string; | ||
setup?: (di: DiContainerForSetup) => void | Promise<void>; | ||
causesSideEffects?: boolean; | ||
lifecycle?: ILifecycle; | ||
injectionToken?: InjectionToken<InjectionTokenInstance, InstantiationParam>; | ||
instantiate: Instantiate<InjectionInstance, InstantiationParam>; | ||
lifecycle: ILifecycle<InstantiationParam>; | ||
} | ||
interface InjectionTokenInjectable< | ||
TInjectionToken extends InjectionToken<unknown, unknown>, | ||
> extends CommonInjectable { | ||
injectionToken: TInjectionToken; | ||
type InjectableLifecycle<InstantiationParam> = InstantiationParam extends void | ||
? { | ||
lifecycle?: ILifecycle<void>; | ||
} | ||
: { | ||
lifecycle: ILifecycle<InstantiationParam>; | ||
}; | ||
instantiate: ( | ||
di: DiContainerForInstantiate, | ||
instantiationParameter: InferFromToken<TInjectionToken>[1], | ||
) => InferFromToken<TInjectionToken>[0]; | ||
} | ||
type GetInjectableOptions< | ||
InjectionInstance extends InjectionTokenInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam, | ||
> = InjectableLifecycle<InstantiationParam> & | ||
Omit< | ||
Injectable<InjectionInstance, InjectionTokenInstance, InstantiationParam>, | ||
'lifecycle' | ||
>; | ||
interface NormalInjectable<TInstance, TInstantiationParameter> | ||
extends CommonInjectable { | ||
instantiate: ( | ||
di: DiContainerForInstantiate, | ||
instantiationParameter: TInstantiationParameter, | ||
) => TInstance; | ||
} | ||
export function getInjectable< | ||
TInjectionToken extends InjectionToken<unknown, unknown>, | ||
InjectionInstance extends InjectionTokenInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam = void, | ||
>( | ||
options: InjectionTokenInjectable<TInjectionToken>, | ||
): InjectionTokenInjectable<TInjectionToken>; | ||
options: GetInjectableOptions< | ||
InjectionInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>, | ||
): Injectable<InjectionInstance, InjectionTokenInstance, InstantiationParam>; | ||
export function getInjectable<TInstance, TInstantiationParameter>( | ||
options: NormalInjectable<TInstance, TInstantiationParameter>, | ||
): NormalInjectable<TInstance, TInstantiationParameter>; | ||
export function getInjectionToken< | ||
InjectionInstance, | ||
InstantiationParam = void, | ||
>({ id: string }): InjectionToken<InjectionInstance, InstantiationParam>; | ||
export function getInjectionToken<TInstance, TInstantiationParameter = void>({ | ||
id: string, | ||
}): InjectionToken<TInstance, TInstantiationParameter>; | ||
type SelectiveAsync< | ||
IsAsync extends boolean, | ||
InjectionInstance, | ||
> = IsAsync extends true ? Promise<InjectionInstance> : InjectionInstance; | ||
export interface Injectable< | ||
TInjectionToken, | ||
TInstance, | ||
TInstantiationParameter, | ||
> { | ||
id: string; | ||
setup?: (di: DiContainerForSetup) => void | Promise<void>; | ||
causesSideEffects?: boolean; | ||
lifecycle?: ILifecycle; | ||
injectionToken?: TInjectionToken; | ||
interface Inject<IsAsync extends boolean> { | ||
<InjectionInstance>( | ||
key: | ||
| Injectable<InjectionInstance, unknown, void> | ||
| InjectionToken<InjectionInstance, void>, | ||
): SelectiveAsync<IsAsync, InjectionInstance>; | ||
<InjectionInstance, InstantiationParam>( | ||
key: | ||
| Injectable<InjectionInstance, unknown, InstantiationParam> | ||
| InjectionToken<InjectionInstance, InstantiationParam>, | ||
param: InstantiationParam, | ||
): SelectiveAsync<IsAsync, InjectionInstance>; | ||
} | ||
instantiate: ( | ||
di: DiContainerForInstantiate, | ||
instantiationParameter: TInstantiationParameter, | ||
) => TInstance; | ||
interface InjectMany<IsAsync extends boolean> { | ||
<InjectionInstance>( | ||
key: | ||
| Injectable<InjectionInstance, unknown, void> | ||
| InjectionToken<InjectionInstance, void>, | ||
): SelectiveAsync<IsAsync, InjectionInstance[]>; | ||
<InjectionInstance, InstantiationParam>( | ||
key: | ||
| Injectable<InjectionInstance, unknown, InstantiationParam> | ||
| InjectionToken<InjectionInstance, InstantiationParam>, | ||
param: InstantiationParam, | ||
): SelectiveAsync<IsAsync, InjectionInstance[]>; | ||
} | ||
interface DiContainerForInjection<TReturnAsPromise extends boolean> { | ||
inject<TInjectable extends NormalInjectable<unknown, unknown>>( | ||
injectableKey: TInjectable, | ||
...instantiationParameter: TentativeTuple< | ||
InferFromInjectable<TInjectable>[1] | ||
> | ||
): TReturnAsPromise extends true | ||
? Promise<InferFromInjectable<TInjectable>[0]> | ||
: InferFromInjectable<TInjectable>[0]; | ||
inject: Inject<TReturnAsPromise>; | ||
injectMany: InjectMany<TReturnAsPromise>; | ||
inject<TInjectionToken extends InjectionToken<unknown, unknown>>( | ||
injectionToken: TInjectionToken, | ||
...instantiationParameter: TentativeTuple< | ||
TInjectionToken['instantiationParameter'] | ||
> | ||
): TReturnAsPromise extends true | ||
? Promise<TInjectionToken['template']> | ||
: TInjectionToken['template']; | ||
register< | ||
InjectionInstance extends InjectionTokenInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam, | ||
>( | ||
injectable: Injectable< | ||
InjectionInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>, | ||
): void; | ||
} | ||
injectMany<TInjectionToken extends InjectionToken<unknown, unknown>>( | ||
injectionToken: TInjectionToken, | ||
...instantiationParameter: TentativeTuple< | ||
TInjectionToken['instantiationParameter'] | ||
> | ||
): TReturnAsPromise extends true | ||
? Promise<TInjectionToken['template'][]> | ||
: TInjectionToken['template'][]; | ||
export interface ILifecycle<InstantiationParam> { | ||
getInstanceKey: (di: DiContainer, params: InstantiationParam) => any; | ||
} | ||
type InferFromToken<T> = T extends InjectionToken< | ||
infer TInstance, | ||
infer TInstantiationParameter | ||
> | ||
? [TInstance, TInstantiationParameter] | ||
: never; | ||
const storedInstanceKey: unique symbol; | ||
const nonStoredInstanceKey: unique symbol; | ||
export interface ILifecycle { | ||
getInstanceKey: (di: DiContainer, param: unknown) => string | number; | ||
} | ||
// ASDs | ||
export const lifecycleEnum: { | ||
singleton: ILifecycle; | ||
singleton: (di: DiContainer, param: void) => typeof storedInstanceKey; | ||
keyedSingleton: <TInstantiationParameter>(keyedSingletonOptions: { | ||
getInstanceKey: ( | ||
di: DiContainer, | ||
instantiationParameter: TInstantiationParameter, | ||
) => string | number; | ||
}) => ILifecycle; | ||
keyedSingleton<InstantiationParam>( | ||
options: ILifecycle<InstantiationParam>, | ||
): typeof options; | ||
transient: ILifecycle; | ||
transient: { | ||
getInstanceKey: (di: DiContainer) => typeof nonStoredInstanceKey; | ||
}; | ||
}; | ||
export function createContainer(...getRequireContexts: any[]): DiContainer; | ||
export function registerErrorMonitoring(di: DiContainer): void; | ||
export const errorMonitorInjectionToken: InjectionToken< | ||
(error: { | ||
context: { id: string; instantiationParameter: any }[]; | ||
error: any; | ||
}) => void | Promise<void>, | ||
void | ||
>; | ||
interface Customizer { | ||
shouldCustomize: (instance: any) => boolean; | ||
// Todo: add proper typing | ||
customizeLink: (link: any) => void; | ||
customizeNode: (node: any) => void; | ||
} | ||
export const dependencyGraphCustomizerToken: InjectionToken<Customizer, void>; | ||
export function registerDependencyGraphing(di: DiContainer): void; | ||
export const plantUmlDependencyGraphInjectable: InjectionToken<string, void>; | ||
} |
{ | ||
"name": "@ogre-tools/injectable", | ||
"private": false, | ||
"version": "5.0.1", | ||
"version": "5.1.0", | ||
"description": "A brutal dependency injection container", | ||
@@ -13,4 +13,4 @@ "repository": { | ||
"keywords": [ | ||
"js", | ||
"functional-programming" | ||
"dependency-injection", | ||
"js" | ||
], | ||
@@ -20,9 +20,6 @@ "author": "Ogre Works", | ||
"dependencies": { | ||
"@ogre-tools/fp": "^5.0.1", | ||
"@ogre-tools/fp": "^5.1.0", | ||
"lodash": "^4.17.21" | ||
}, | ||
"scripts": { | ||
"build": "webpack" | ||
}, | ||
"gitHead": "5e4de86e2f77a9b9a159192959741bc308751027", | ||
"gitHead": "862acd435ed8aca0e32b448e3af0d4e0574866c5", | ||
"bugs": { | ||
@@ -29,0 +26,0 @@ "url": "https://github.com/ogre-works/ogre-tools/issues" |
import getInjectionToken from '../getInjectionToken/getInjectionToken'; | ||
import getInjectable from '../getInjectable/getInjectable'; | ||
import getDi from '../test-utils/getDiForUnitTesting'; | ||
import lifecycleEnum from './lifecycleEnum'; | ||
@@ -119,3 +120,3 @@ describe('createContainer.injection-token', () => { | ||
const someOtherInjectionToken = getInjectionToken({ | ||
id: 'some-injection-token', | ||
id: 'some-other-injection-token', | ||
}); | ||
@@ -126,3 +127,3 @@ | ||
injectionToken: someOtherInjectionToken, | ||
instantiate: di => di.injectMany(parentInjectable), | ||
instantiate: di => di.injectMany(someInjectionToken), | ||
}); | ||
@@ -133,3 +134,3 @@ | ||
injectionToken: someInjectionToken, | ||
instantiate: di => di.injectMany(childInjectable), | ||
instantiate: di => di.injectMany(someOtherInjectionToken), | ||
}); | ||
@@ -140,5 +141,5 @@ | ||
expect(() => { | ||
di.injectMany(parentInjectable, undefined, ['some-bogus-context']); | ||
di.injectMany(someInjectionToken); | ||
}).toThrow( | ||
'Cycle of injectables encountered: "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"', | ||
'Cycle of injectables encountered: "some-injection-token" -> "some-parent-injectable" -> "some-other-injection-token" -> "some-child-injectable" -> "some-injection-token"', | ||
); | ||
@@ -160,2 +161,30 @@ }); | ||
}); | ||
it('given injectables with a dependency cycle, when injecting many with custom root context, throws error with the custom context', () => { | ||
const injectionToken = getInjectionToken({ id: 'some-injection-token' }); | ||
const childInjectable = getInjectable({ | ||
id: 'some-child-injectable', | ||
instantiate: di => di.inject(parentInjectable), | ||
}); | ||
const parentInjectable = getInjectable({ | ||
id: 'some-parent-injectable', | ||
instantiate: di => di.inject(childInjectable), | ||
injectionToken, | ||
}); | ||
const di = getDi(parentInjectable, childInjectable); | ||
expect(() => { | ||
di.injectMany(injectionToken, undefined, { | ||
injectable: { | ||
id: 'some-custom-context-id', | ||
lifecycle: lifecycleEnum.transient, | ||
}, | ||
}); | ||
}).toThrow( | ||
'Cycle of injectables encountered: "some-custom-context-id" -> "some-injection-token" -> "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"', | ||
); | ||
}); | ||
}); |
@@ -0,3 +1,4 @@ | ||
import getDi from '../test-utils/getDiForUnitTesting'; | ||
import getInjectable from '../getInjectable/getInjectable'; | ||
import getDi from '../test-utils/getDiForUnitTesting'; | ||
import lifecycleEnum from './lifecycleEnum'; | ||
@@ -52,6 +53,6 @@ describe('createContainer.injection', () => { | ||
it('given async injectables with a dependency cycle, when injected, throws', () => { | ||
it('given sync injectables with a dependency cycle, when injected with custom root context, throws error with the custom context', () => { | ||
const childInjectable = getInjectable({ | ||
id: 'some-child-injectable', | ||
instantiate: async di => await di.inject(parentInjectable), | ||
instantiate: di => di.inject(parentInjectable), | ||
}); | ||
@@ -61,3 +62,3 @@ | ||
id: 'some-parent-injectable', | ||
instantiate: async di => await di.inject(childInjectable), | ||
instantiate: di => di.inject(childInjectable), | ||
}); | ||
@@ -67,13 +68,18 @@ | ||
const actualPromise = di.inject(parentInjectable); | ||
return expect(actualPromise).rejects.toThrow( | ||
'Cycle of injectables encountered: "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"', | ||
expect(() => { | ||
di.inject(parentInjectable, undefined, { | ||
injectable: { | ||
id: 'some-custom-context-id', | ||
lifecycle: lifecycleEnum.transient, | ||
}, | ||
}); | ||
}).toThrow( | ||
'Cycle of injectables encountered: "some-custom-context-id" -> "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"', | ||
); | ||
}); | ||
it('given injectables with a dependency cycle, when injected with bogus context, throws error without bogus context', () => { | ||
it('given async injectables with a dependency cycle, when injected, throws', () => { | ||
const childInjectable = getInjectable({ | ||
id: 'some-child-injectable', | ||
instantiate: di => di.inject(parentInjectable), | ||
instantiate: async di => await di.inject(parentInjectable), | ||
}); | ||
@@ -83,3 +89,3 @@ | ||
id: 'some-parent-injectable', | ||
instantiate: di => di.inject(childInjectable), | ||
instantiate: async di => await di.inject(childInjectable), | ||
}); | ||
@@ -89,5 +95,5 @@ | ||
expect(() => { | ||
di.inject(parentInjectable, undefined, ['some-bogus-context']); | ||
}).toThrow( | ||
const actualPromise = di.inject(parentInjectable); | ||
return expect(actualPromise).rejects.toThrow( | ||
'Cycle of injectables encountered: "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"', | ||
@@ -94,0 +100,0 @@ ); |
@@ -1,2 +0,1 @@ | ||
import get from 'lodash/fp/get'; | ||
import conforms from 'lodash/fp/conforms'; | ||
@@ -8,16 +7,23 @@ import filter from 'lodash/fp/filter'; | ||
import forEach from 'lodash/fp/forEach'; | ||
import get from 'lodash/fp/get'; | ||
import getCycles from './getCycles/getCycles'; | ||
import getInjectionToken from '../getInjectionToken/getInjectionToken'; | ||
import has from 'lodash/fp/has'; | ||
import invoke from 'lodash/fp/invoke'; | ||
import isFunction from 'lodash/fp/isFunction'; | ||
import isUndefined from 'lodash/fp/isUndefined'; | ||
import join from 'lodash/fp/join'; | ||
import last from 'lodash/fp/last'; | ||
import { nonStoredInstanceKey } from './lifecycleEnum'; | ||
import map from 'lodash/fp/map'; | ||
import matches from 'lodash/fp/matches'; | ||
import not from 'lodash/fp/negate'; | ||
import isEqual from 'lodash/fp/isEqual'; | ||
import once from 'lodash/fp/once'; | ||
import reject from 'lodash/fp/reject'; | ||
import sortBy from 'lodash/fp/sortBy'; | ||
import last from 'lodash/fp/last'; | ||
import join from 'lodash/fp/join'; | ||
import reject from 'lodash/fp/reject'; | ||
import tap from 'lodash/fp/tap'; | ||
import { pipeline } from '@ogre-tools/fp'; | ||
import isUndefined from 'lodash/fp/isUndefined'; | ||
import lifecycleEnum, { nonStoredInstanceKey } from './lifecycleEnum'; | ||
import getCycles from './getCycles/getCycles'; | ||
import curry from 'lodash/fp/curry'; | ||
import overSome from 'lodash/fp/overSome'; | ||
@@ -33,48 +39,95 @@ export default (...listOfGetRequireContexts) => { | ||
const privateDi = { | ||
inject: (alias, instantiationParameter, context = []) => { | ||
const originalInjectable = getRelatedInjectable({ | ||
const privateInject = (alias, instantiationParameter, context = []) => { | ||
checkForTooManyMatches(injectables, alias); | ||
const relatedInjectables = getRelatedInjectables({ injectables, alias }); | ||
if (relatedInjectables.length === 0 && alias.adHoc === true) { | ||
privateDi.register(alias); | ||
} else { | ||
checkForNoMatches(injectables, alias, context); | ||
} | ||
const originalInjectable = getRelatedInjectable({ | ||
injectables, | ||
alias, | ||
context, | ||
}); | ||
const overriddenInjectable = getOverridingInjectable({ | ||
overridingInjectables, | ||
alias, | ||
}); | ||
const injectable = overriddenInjectable || originalInjectable; | ||
if (!setupsHaveBeenRan && !setupsAreBeingRan && injectable.setup) { | ||
throw new Error( | ||
`Tried to inject setuppable "${injectable.id}" before setups are ran.`, | ||
); | ||
} | ||
if (sideEffectsArePrevented && injectable.causesSideEffects) { | ||
throw new Error( | ||
`Tried to inject "${injectable.id}" when side-effects are prevented.`, | ||
); | ||
} | ||
return getInstance({ | ||
injectable, | ||
instantiationParameter, | ||
di: privateDi, | ||
injectableMap, | ||
context, | ||
injectMany: nonDecoratedPrivateInjectMany, | ||
}); | ||
}; | ||
const nonDecoratedPrivateInjectMany = ( | ||
injectionToken, | ||
instantiationParameter, | ||
oldContext = [], | ||
) => { | ||
const newContext = [...oldContext, { injectable: injectionToken }]; | ||
return pipeline( | ||
getRelatedInjectables({ | ||
injectables, | ||
alias, | ||
context, | ||
}); | ||
alias: injectionToken, | ||
}), | ||
const overriddenInjectable = getOverridingInjectable({ | ||
overridingInjectables, | ||
alias, | ||
}); | ||
map(injectable => | ||
decoratedPrivateInject(injectable, instantiationParameter, newContext), | ||
), | ||
); | ||
}; | ||
const injectable = overriddenInjectable || originalInjectable; | ||
const withInjectionDecorators = withInjectionDecoratorsFor({ | ||
injectMany: nonDecoratedPrivateInjectMany, | ||
}); | ||
if (!setupsHaveBeenRan && !setupsAreBeingRan && injectable.setup) { | ||
const decoratedPrivateInject = withInjectionDecorators(privateInject); | ||
const decoratedPrivateInjectMany = withInjectionDecorators( | ||
nonDecoratedPrivateInjectMany, | ||
); | ||
const privateDi = { | ||
inject: decoratedPrivateInject, | ||
injectMany: decoratedPrivateInjectMany, | ||
register: externalInjectable => { | ||
if (setupsHaveBeenRan && !!externalInjectable.setup) { | ||
throw new Error( | ||
`Tried to inject setuppable "${injectable.id}" before setups are ran.`, | ||
`Tried to register setuppable "${externalInjectable.id}" after setups have already ran.`, | ||
); | ||
} | ||
if (sideEffectsArePrevented && injectable.causesSideEffects) { | ||
if (setupsAreBeingRan && !!externalInjectable.setup) { | ||
throw new Error( | ||
`Tried to inject "${injectable.id}" when side-effects are prevented.`, | ||
`Tried to register setuppable "${externalInjectable.id}" during setup.`, | ||
); | ||
} | ||
return getInstance({ | ||
injectable, | ||
instantiationParameter, | ||
di: privateDi, | ||
injectableMap, | ||
context, | ||
}); | ||
}, | ||
injectMany: (alias, instantiationParameter, context = []) => | ||
pipeline( | ||
getRelatedInjectables({ injectables, alias }), | ||
map(injectable => | ||
privateDi.inject(injectable, instantiationParameter, context), | ||
), | ||
), | ||
register: externalInjectable => { | ||
if (!externalInjectable.id) { | ||
@@ -90,14 +143,9 @@ throw new Error('Tried to register injectable without ID.'); | ||
const lifecycle = externalInjectable.lifecycle || lifecycleEnum.singleton; | ||
const internalInjectable = { | ||
...externalInjectable, | ||
lifecycle, | ||
...(externalInjectable.setup | ||
? { setup: once(externalInjectable.setup) } | ||
: {}), | ||
// Todo: spread-ternary | ||
setup: externalInjectable.setup | ||
? once(externalInjectable.setup) | ||
: undefined, | ||
permitSideEffects: function () { | ||
@@ -148,6 +196,8 @@ this.causesSideEffects = false; | ||
const diForSetupsFor = setuppable => ({ | ||
injectMany: publicDi.injectMany, | ||
inject: async (alias, parameter) => { | ||
const targetSetuppable = injectables.find( | ||
conforms({ | ||
id: x => x === alias.id, | ||
id: isEqual(alias.id), | ||
setup: isFunction, | ||
@@ -178,4 +228,11 @@ }), | ||
return privateDi.inject(alias, parameter); | ||
return privateDi.inject(alias, parameter, [ | ||
{ | ||
injectable: setuppable, | ||
isSetup: true, | ||
}, | ||
]); | ||
}, | ||
register: privateDi.register, | ||
}); | ||
@@ -229,4 +286,16 @@ | ||
...privateDi, | ||
inject: (alias, parameter) => privateDi.inject(alias, parameter), | ||
injectMany: (alias, parameter) => privateDi.injectMany(alias, parameter), | ||
inject: (alias, parameter, customContextItem) => | ||
privateDi.inject( | ||
alias, | ||
parameter, | ||
customContextItem ? [customContextItem] : undefined, | ||
), | ||
injectMany: (alias, parameter, customContextItem) => | ||
privateDi.injectMany( | ||
alias, | ||
parameter, | ||
customContextItem ? [customContextItem] : undefined, | ||
), | ||
}; | ||
@@ -249,32 +318,11 @@ | ||
const isRelatedTo = alias => injectable => | ||
injectable.id === alias.id || | ||
(injectable.injectionToken && injectable.injectionToken === alias); | ||
const isRelatedTo = curry( | ||
(alias, injectable) => | ||
injectable.id === alias.id || | ||
(injectable.injectionToken && injectable.injectionToken === alias), | ||
); | ||
const getRelatedInjectable = ({ injectables, alias, context }) => { | ||
const relatedInjectables = getRelatedInjectables({ injectables, alias }); | ||
const getRelatedInjectable = ({ injectables, alias }) => | ||
pipeline(getRelatedInjectables({ injectables, alias }), first); | ||
if (relatedInjectables.length === 0) { | ||
const errorContextString = [...context, { id: alias.id }] | ||
.map(get('id')) | ||
.join('" -> "'); | ||
throw new Error( | ||
`Tried to inject non-registered injectable "${errorContextString}".`, | ||
); | ||
} | ||
if (relatedInjectables.length > 1) { | ||
throw new Error( | ||
`Tried to inject single injectable for injection token "${ | ||
alias.id | ||
}" but found multiple injectables: "${relatedInjectables | ||
.map(relatedInjectable => relatedInjectable.id) | ||
.join('", "')}"`, | ||
); | ||
} | ||
return first(relatedInjectables); | ||
}; | ||
const getRelatedInjectables = ({ injectables, alias }) => | ||
@@ -299,7 +347,18 @@ pipeline(injectables, filter(isRelatedTo(alias))); | ||
const newContext = [...oldContext, { id: injectable.id }]; | ||
const newContext = [ | ||
...oldContext, | ||
{ | ||
injectable, | ||
instantiationParameter, | ||
}, | ||
]; | ||
const injectableCausingCycle = pipeline( | ||
oldContext, | ||
find({ id: injectable.id }), | ||
find( | ||
contextItem => | ||
contextItem.injectable.id === injectable.id && | ||
contextItem.isSetup !== true, | ||
), | ||
); | ||
@@ -310,3 +369,3 @@ | ||
`Cycle of injectables encountered: "${newContext | ||
.map(get('id')) | ||
.map(get('injectable.id')) | ||
.join('" -> "')}"`, | ||
@@ -323,2 +382,6 @@ ); | ||
di.injectMany(alias, parameter, newContext), | ||
context: newContext, | ||
register: di.register, | ||
}; | ||
@@ -337,3 +400,12 @@ | ||
const newInstance = injectable.instantiate( | ||
const instantiateWithDecorators = pipeline( | ||
injectable.instantiate, | ||
withInstantiationDecoratorsFor({ | ||
injectMany: di.injectMany, | ||
injectable, | ||
}), | ||
); | ||
const newInstance = instantiateWithDecorators( | ||
minimalDi, | ||
@@ -349,1 +421,93 @@ ...(isUndefined(instantiationParameter) ? [] : [instantiationParameter]), | ||
}; | ||
export const instantiationDecoratorToken = getInjectionToken({ | ||
id: 'instantiate-decorator-token', | ||
decorable: false, | ||
}); | ||
export const injectionDecoratorToken = getInjectionToken({ | ||
id: 'injection-decorator-token', | ||
decorable: false, | ||
}); | ||
const withInstantiationDecoratorsFor = ({ injectMany, injectable }) => { | ||
const isRelevantDecorator = isRelevantDecoratorFor(injectable); | ||
return toBeDecorated => | ||
(...args) => { | ||
if (injectable.decorable === false) { | ||
return toBeDecorated(...args); | ||
} | ||
const decorators = pipeline( | ||
injectMany(instantiationDecoratorToken), | ||
filter(isRelevantDecorator), | ||
map('decorate'), | ||
); | ||
return pipeline(toBeDecorated, ...decorators)(...args); | ||
}; | ||
}; | ||
const withInjectionDecoratorsFor = | ||
({ injectMany }) => | ||
toBeDecorated => | ||
(alias, ...args) => { | ||
if (alias.decorable === false) { | ||
return toBeDecorated(alias, ...args); | ||
} | ||
const isRelevantDecorator = isRelevantDecoratorFor(alias); | ||
const decorators = pipeline( | ||
injectMany(injectionDecoratorToken), | ||
filter(isRelevantDecorator), | ||
map('decorate'), | ||
); | ||
return pipeline(toBeDecorated, ...decorators)(alias, ...args); | ||
}; | ||
const isGlobalDecorator = not(has('target')); | ||
const isTargetedDecoratorFor = injectable => | ||
conforms({ | ||
target: alias => isRelatedTo(alias, injectable), | ||
}); | ||
const isRelevantDecoratorFor = injectable => | ||
overSome([isGlobalDecorator, isTargetedDecoratorFor(injectable)]); | ||
const checkForNoMatches = (injectables, alias, context) => { | ||
const relatedInjectables = getRelatedInjectables({ | ||
injectables, | ||
alias, | ||
}); | ||
if (relatedInjectables.length === 0) { | ||
const errorContextString = [...context, { injectable: { id: alias.id } }] | ||
.map(get('injectable.id')) | ||
.join('" -> "'); | ||
throw new Error( | ||
`Tried to inject non-registered injectable "${errorContextString}".`, | ||
); | ||
} | ||
}; | ||
const checkForTooManyMatches = (injectables, alias) => { | ||
const relatedInjectables = getRelatedInjectables({ | ||
injectables, | ||
alias, | ||
}); | ||
if (relatedInjectables.length > 1) { | ||
throw new Error( | ||
`Tried to inject single injectable for injection token "${ | ||
alias.id | ||
}" but found multiple injectables: "${relatedInjectables | ||
.map(relatedInjectable => relatedInjectable.id) | ||
.join('", "')}"`, | ||
); | ||
} | ||
}; |
@@ -6,5 +6,6 @@ import asyncFn from '@async-fn/jest'; | ||
import getDi from '../test-utils/getDiForUnitTesting'; | ||
import getInjectionToken from '../getInjectionToken/getInjectionToken'; | ||
describe('createContainer.setuppable', () => { | ||
it('given setuppables with a dependency cycle, when setupped, throws most complex cycle in system', () => { | ||
it('given setuppables with a dependency cycle when injecting single, when setupped, throws most complex cycle in system', () => { | ||
const someRootSetuppable = getInjectable({ | ||
@@ -43,3 +44,35 @@ id: 'some-root-injectable', | ||
it('given setup for injectable, when setups are ran, runs the setup with a way to inject', async () => { | ||
xit('given setuppables with a dependency cycle when injecting many, when setupped, throws most complex cycle in system', async () => { | ||
const someToken = getInjectionToken({ id: 'some-token' }); | ||
const someSetuppable = getInjectable({ | ||
id: 'some-injectable', | ||
setup: async di => { | ||
await di.injectMany(someOtherToken); | ||
}, | ||
instantiate: () => 'irrelevant', | ||
injectionToken: someToken, | ||
}); | ||
const someOtherToken = getInjectionToken({ id: 'some-other-token' }); | ||
const someOtherSetuppable = getInjectable({ | ||
id: 'some-other-injectable', | ||
setup: async di => { | ||
await di.injectMany(someToken); | ||
}, | ||
instantiate: () => 'irrelevant', | ||
injectionToken: someOtherToken, | ||
}); | ||
const di = getDi(someSetuppable, someOtherSetuppable); | ||
return expect(di.runSetups()).rejects.toThrow( | ||
'Cycle of setuppables encountered: "some-root-injectable" -> "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"', | ||
); | ||
}); | ||
it('given setup for injectable, when setups are ran, runs the setup with a ways to inject', async () => { | ||
let instanceFromSetup; | ||
@@ -51,3 +84,6 @@ | ||
setup: async di => { | ||
instanceFromSetup = await di.inject(someInjectable, 'some-parameter'); | ||
instanceFromSetup = { | ||
single: await di.inject(someInjectable, 'some-parameter'), | ||
many: await di.injectMany(someInjectionToken, 'some-parameter'), | ||
}; | ||
}, | ||
@@ -59,10 +95,24 @@ }); | ||
lifecycle: lifecycleEnum.transient, | ||
instantiate: (di, parameter) => `some-instance: "${parameter}"`, | ||
instantiate: (di, parameter) => `some-single-instance: "${parameter}"`, | ||
}); | ||
const di = getDi(someSetuppable, someInjectable); | ||
const someInjectionToken = getInjectionToken({ | ||
id: 'some-injection-token', | ||
}); | ||
const someTokenInjectable = getInjectable({ | ||
id: 'some-token-injectable', | ||
lifecycle: lifecycleEnum.transient, | ||
instantiate: (di, parameter) => `some-of-many-instances: "${parameter}"`, | ||
injectionToken: someInjectionToken, | ||
}); | ||
const di = getDi(someSetuppable, someInjectable, someTokenInjectable); | ||
await di.runSetups(); | ||
expect(instanceFromSetup).toBe('some-instance: "some-parameter"'); | ||
expect(instanceFromSetup).toEqual({ | ||
single: 'some-single-instance: "some-parameter"', | ||
many: ['some-of-many-instances: "some-parameter"'], | ||
}); | ||
}); | ||
@@ -69,0 +119,0 @@ |
export const nonStoredInstanceKey = Symbol('non-stored-instance-key'); | ||
export const storedInstanceKey = Symbol('stored-instance-key'); | ||
export default { | ||
singleton: { | ||
getInstanceKey: () => 'singleton', | ||
name: 'Singleton', | ||
shortName: 'S', | ||
color: 'lightGreen', | ||
getInstanceKey: () => storedInstanceKey, | ||
}, | ||
keyedSingleton: ({ getInstanceKey }) => ({ getInstanceKey }), | ||
keyedSingleton: ({ getInstanceKey }) => ({ | ||
name: 'Keyed', | ||
shortName: 'K', | ||
color: 'pink', | ||
getInstanceKey, | ||
}), | ||
transient: { | ||
name: 'Transient', | ||
shortName: 'T', | ||
color: 'orchid', | ||
getInstanceKey: () => nonStoredInstanceKey, | ||
}, | ||
}; |
@@ -1,5 +0,6 @@ | ||
import identity from 'lodash/fp/identity'; | ||
import lifecycleEnum from '../dependency-injection-container/lifecycleEnum'; | ||
// Note: this function exists only for typed presence in TypeScript. | ||
// It has little purpose in JavaScript. | ||
export default identity; | ||
export default ({ lifecycle = lifecycleEnum.singleton, ...injectable }) => ({ | ||
lifecycle, | ||
...injectable, | ||
}); |
// Note: this function exists only for typed presence in TypeScript. | ||
// It has little purpose in JavaScript. | ||
export default ({ id }) => ({ id }); | ||
export const injectionTokenSymbol = Symbol('injection-token'); | ||
export default ({ id, decorable = true }) => ({ | ||
id, | ||
aliasType: injectionTokenSymbol, | ||
decorable, | ||
}); |
Sorry, the diff of this file is too big to display
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
4124489
38
77357
1
1
Updated@ogre-tools/fp@^5.1.0