Skip to content

Commit

Permalink
feat: Introduce access to already injected instances
Browse files Browse the repository at this point in the history
This is interesting mostly with keyedSingletons, where inject can happen multiple times with
different keys. Without this feature, accessing said instances again is needlessly problematic.

Disclaimer: getInstances is to be considered experimental, as it could promote suboptimal coding
patterns with eg. more than one responsibility.
  • Loading branch information
Iku-turso committed Apr 11, 2023
1 parent 1e4f944 commit a8ecc66
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 0 deletions.
7 changes: 7 additions & 0 deletions packages/injectable/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ export type InjectWithParameter = <InjectionInstance, InstantiationParam>(

export type Inject = InjectWithoutParameter & InjectWithParameter;

export type GetInstances = <InjectionInstance>(
alias:
| Injectable<InjectionInstance, unknown>
| InjectionToken<InjectionInstance, void>,
) => InjectionInstance[];

export type SpecificInjectWithoutParameter<InjectionInstance> = (
key:
| Injectable<InjectionInstance, unknown>
Expand Down Expand Up @@ -180,6 +186,7 @@ export interface DiContainerForInjection {
register(...injectables: Injectable<any, any, any>[]): void;
deregister(...injectables: Injectable<any, any, any>[]): void;
context: ContextItem[];
getInstances: GetInstances;
}

export interface ILifecycle<InstantiationParam> {
Expand Down
19 changes: 19 additions & 0 deletions packages/injectable/core/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,22 @@ expectError(
someProperty: 'not a number',
})),
);

// given injectable, when getting instances using injectable, typing is OK
expectType<string[]>(
di.getInstances(
getInjectable({
id: 'some-injectable',
instantiate: () => 'irrelevant',
}),
),
);

// given token, when getting instances using token, typing is OK
expectType<number[]>(
di.getInstances(
getInjectionToken<number>({
id: 'some-token',
}),
),
);
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ export default (containerId, { detectCycles = true } = {}) => {
: [containerRootContextItem],
containerRootContextItem.injectable,
),

getInstances: alias =>
getRelatedInjectables(alias).flatMap(injectable => [
...instancesByInjectableMap.get(injectable).values(),
]),
};

return publicDi;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import getInjectable from '../getInjectable/getInjectable';
import createContainer from '../dependency-injection-container/createContainer';
import getInjectionToken from '../getInjectionToken/getInjectionToken';
import lifecycleEnum from '../dependency-injection-container/lifecycleEnum';

describe('access-to-already-injected-instances', () => {
it('given an injectable but not registered, when accessing instances of said injectable, returns empty list', () => {
const di = createContainer('irrelevant');

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: () => 'some-instance',
});

const instances = di.getInstances(someInjectable);

expect(instances).toEqual([]);
});

it('given a injectable and registered, but not injected, when accessing instances of said injectable, returns empty list', () => {
const di = createContainer('irrelevant');

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: () => 'some-instance',
});

di.register(someInjectable);

const instances = di.getInstances(someInjectable);

expect(instances).toEqual([]);
});

it('given a singleton injectable and registered and injected, when accessing instances of said injectable, returns list containing the instance', () => {
const di = createContainer('irrelevant');

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: () => 'some-instance',
});

di.register(someInjectable);

di.inject(someInjectable);

const instances = di.getInstances(someInjectable);

expect(instances).toEqual(['some-instance']);
});

it('given a singleton injectable implementing a token and registered and injected, when accessing instances of said token, returns list containing the instance', () => {
const di = createContainer('irrelevant');

const someInjectionToken = getInjectionToken({
id: 'some-injection-token',
});

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: () => 'some-instance',
injectionToken: someInjectionToken,
});

di.register(someInjectable);

di.inject(someInjectable);

const instances = di.getInstances(someInjectionToken);

expect(instances).toEqual(['some-instance']);
});

it('given a transient injectable and registered and injected, when accessing instances of said injectable, returns empty list of instance, because transient instances are not stored', () => {
const di = createContainer('irrelevant');

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: () => 'some-instance',
lifecycle: lifecycleEnum.transient,
});

di.register(someInjectable);

di.inject(someInjectable);

const instances = di.getInstances(someInjectable);

expect(instances).toEqual([]);
});

it('given a keyed singleton injectable and registered and injected with different keys, when accessing instances of said injectable, returns list containing the instances', () => {
const di = createContainer('irrelevant');

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: (di, key) => `some-instance-for-${key}`,
lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, key) => key,
}),
});

di.register(someInjectable);

di.inject(someInjectable, 'some-key-1');
di.inject(someInjectable, 'some-key-2');

const instances = di.getInstances(someInjectable);

expect(instances).toEqual([
'some-instance-for-some-key-1',
'some-instance-for-some-key-2',
]);
});

it('given a keyed singleton injectable implementing a token and registered and injected with different keys, when accessing instances of said token, returns list containing the instances', () => {
const di = createContainer('irrelevant');

const someInjectionToken = getInjectionToken({
id: 'some-injection-token',
});

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: (di, key) => `some-instance-for-${key}`,

lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, key) => key,
}),

injectionToken: someInjectionToken,
});

di.register(someInjectable);

di.inject(someInjectionToken, 'some-key-1');
di.inject(someInjectionToken, 'some-key-2');

const instances = di.getInstances(someInjectable);

expect(instances).toEqual([
'some-instance-for-some-key-1',
'some-instance-for-some-key-2',
]);
});

it('given a keyed singleton injectable implementing a token and registered and "injected many" with different keys, when accessing instances of said token, returns list containing the instances', () => {
const di = createContainer('irrelevant');

const someInjectionToken = getInjectionToken({
id: 'some-injection-token',
});

const someInjectable = getInjectable({
id: 'some-injectable',
instantiate: (di, key) => `some-instance-for-${key}`,

lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, key) => key,
}),

injectionToken: someInjectionToken,
});

di.register(someInjectable);

di.injectMany(someInjectionToken, 'some-key-1');
di.injectMany(someInjectionToken, 'some-key-2');

const instances = di.getInstances(someInjectable);

expect(instances).toEqual([
'some-instance-for-some-key-1',
'some-instance-for-some-key-2',
]);
});
});

0 comments on commit a8ecc66

Please sign in to comment.