Skip to content

Commit

Permalink
feat: Introduce "di.injectManyWithMeta()" to permit reuse of injectab…
Browse files Browse the repository at this point in the history
…le id in implementation code
  • Loading branch information
Iku-turso committed Feb 3, 2023
1 parent 19c1bb1 commit 94fed1e
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 8 deletions.
25 changes: 25 additions & 0 deletions packages/injectable/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,30 @@ interface InjectMany {
): InjectionInstance[];
}

type Meta = {
id: string;
};

export type InjectionInstanceWithMeta<InjectionInstance> = {
instance: InjectionInstance;
meta: Meta;
};

interface InjectManyWithMeta {
<InjectionInstance>(
key:
| Injectable<InjectionInstance, unknown, void>
| InjectionToken<InjectionInstance, void>,
): InjectionInstanceWithMeta<InjectionInstance>[];

<InjectionInstance, InstantiationParam>(
key:
| Injectable<InjectionInstance, unknown, InstantiationParam>
| InjectionToken<InjectionInstance, InstantiationParam>,
param: InstantiationParam,
): InjectionInstanceWithMeta<InjectionInstance>[];
}

interface ContextItem {
injectable: Injectable<any, any, any>;
instantiationParameter: unknown;
Expand All @@ -133,6 +157,7 @@ interface ContextItem {
export interface DiContainerForInjection {
inject: Inject;
injectMany: InjectMany;
injectManyWithMeta: InjectManyWithMeta;
register(...injectables: Injectable<any, any, any>[]): void;
deregister(...injectables: Injectable<any, any, any>[]): void;
context: ContextItem[];
Expand Down
12 changes: 12 additions & 0 deletions packages/injectable/core/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ expectType<{ someGeneralProperty: string }[]>(
di.injectMany(someTokenWithGeneralProperty),
);

// given injecting many with meta, typing is OK
expectType<
{
instance: {
requiredProperty: string;
optionalProperty?: number;
};

meta: { id: string };
}[]
>(di.injectManyWithMeta(someInjectionToken));

const someOtherInjectionToken = getInjectionToken<{ someProperty: number }>({
id: 'some-other-injection-token',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,22 @@ export default containerId => {

const containerRootContextItem = { injectable: { id: containerId } };

const nonDecoratedPrivateInjectMany = nonDecoratedPrivateInjectManyFor({
containerRootContextItem,
getRelatedInjectables,
getInject: () => privateInject,
});
const nonDecoratedPrivateInjectManyForUnknownMeta =
nonDecoratedPrivateInjectManyFor({
containerRootContextItem,
getRelatedInjectables,
getInject: () => privateInject,
});

const nonDecoratedPrivateInjectMany =
nonDecoratedPrivateInjectManyForUnknownMeta({
withMeta: false,
});

const nonDecoratedPrivateInjectManyWithMeta =
nonDecoratedPrivateInjectManyForUnknownMeta({
withMeta: true,
});

const withInjectionDecorators = withInjectionDecoratorsFor({
injectMany: nonDecoratedPrivateInjectMany,
Expand All @@ -57,6 +68,10 @@ export default containerId => {
nonDecoratedPrivateInjectMany,
);

const decoratedPrivateInjectManyWithMeta = withInjectionDecorators(
nonDecoratedPrivateInjectManyWithMeta,
);

const registerSingle = registerSingleFor({
injectableMap,
instancesByInjectableMap,
Expand Down Expand Up @@ -99,6 +114,7 @@ export default containerId => {
const privateDi = {
inject: privateInject,
injectMany: decoratedPrivateInjectMany,
injectManyWithMeta: decoratedPrivateInjectManyWithMeta,
register,
deregister,
decorate,
Expand Down Expand Up @@ -141,6 +157,15 @@ export default containerId => {
? [containerRootContextItem, customContextItem]
: [containerRootContextItem],
),

injectManyWithMeta: (alias, parameter, customContextItem) =>
privateDi.injectManyWithMeta(
alias,
parameter,
customContextItem
? [containerRootContextItem, customContextItem]
: [containerRootContextItem],
),
};

return publicDi;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ const getInstance = ({
injectMany: (alias, parameter) =>
di.injectMany(alias, parameter, newContext),

injectManyWithMeta: (alias, parameter) =>
di.injectManyWithMeta(alias, parameter, newContext),

context: newContext,

register: (...injectables) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { isPromise } from '@ogre-tools/fp';

export const nonDecoratedPrivateInjectManyFor =
({ containerRootContextItem, getRelatedInjectables, getInject }) =>
({ withMeta }) =>
(
injectionToken,
instantiationParameter,
Expand All @@ -13,9 +14,25 @@ export const nonDecoratedPrivateInjectManyFor =

const relatedInjectables = getRelatedInjectables(injectionToken);

const injected = relatedInjectables.map(injectable =>
inject(injectable, instantiationParameter, newContext),
);
const injected = relatedInjectables.map(injectable => {
const instance = inject(injectable, instantiationParameter, newContext);

if (!withMeta) {
return instance;
}

if (!isPromise(instance)) {
return {
instance,
meta: { id: injectable.id },
};
}

return instance.then(awaitedInstance => ({
instance: awaitedInstance,
meta: { id: injectable.id },
}));
});

if (injected.find(isPromise)) {
return Promise.all(injected);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import getInjectable from '../getInjectable/getInjectable';
import lifecycleEnum from '../dependency-injection-container/lifecycleEnum';
import createContainer from '../dependency-injection-container/createContainer';
import getInjectionToken from '../getInjectionToken/getInjectionToken';

describe('injection with meta data', () => {
it('given injecting many sync injectable with meta data, does so', () => {
const someInjectionToken = getInjectionToken({
id: 'some-injection-token-id',
});

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

const di = createContainer('irrelevant');

di.register(someInjectable);

const actual = di.injectManyWithMeta(someInjectionToken);

expect(actual).toEqual([
{
instance: 'some-instance',
meta: { id: 'some-injectable' },
},
]);
});

it('given injecting many async injectable with meta data, does so', async () => {
const someInjectionToken = getInjectionToken({
id: 'some-injection-token-id',
});

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

const di = createContainer('irrelevant');

di.register(someInjectable);

const actual = await di.injectManyWithMeta(someInjectionToken);

expect(actual).toEqual([
{
instance: 'some-instance',
meta: { id: 'some-injectable' },
},
]);
});

it('given injecting many sync injectables with meta data from within a child injectable, does so', () => {
const someParentInjectable = getInjectable({
id: 'some-parent-injectable',
instantiate: di => di.injectManyWithMeta(someInjectionToken),
});

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

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

const di = createContainer('irrelevant');

di.register(someParentInjectable, someChildInjectable);

const actual = di.inject(someParentInjectable);

expect(actual).toEqual([
{
instance: 'some-instance',
meta: { id: 'some-child-injectable' },
},
]);
});

it('given injecting many async injectables with meta data from within a child injectable, does so', async () => {
const someParentInjectable = getInjectable({
id: 'some-parent-injectable',
instantiate: async di => await di.injectManyWithMeta(someInjectionToken),
});

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

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

const di = createContainer('irrelevant');

di.register(someParentInjectable, someChildInjectable);

const actual = await di.inject(someParentInjectable);

expect(actual).toEqual([
{
instance: 'some-instance',
meta: { id: 'some-child-injectable' },
},
]);
});

it('given injectables with a dependency cycle, when injecting many with meta and 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),
injectionToken,
});

const parentInjectable = getInjectable({
id: 'some-parent-injectable',
instantiate: di => di.inject(childInjectable),
injectionToken,
});

const di = createContainer('some-container');

di.register(parentInjectable, childInjectable);

expect(() => {
di.injectManyWithMeta(injectionToken, undefined, {
injectable: {
id: 'some-custom-context-id',
},
});
}).toThrow(
'Cycle of injectables encountered: "some-container" -> "some-custom-context-id" -> "some-injection-token" -> "some-parent-injectable" -> "some-child-injectable" -> "some-parent-injectable"',
);
});
});

0 comments on commit 94fed1e

Please sign in to comment.