Skip to content

Commit

Permalink
feat: Implement access to source namespace to permit eg. "scope speci…
Browse files Browse the repository at this point in the history
…fic" keyedSingletons
  • Loading branch information
Iku-turso committed May 16, 2023
1 parent 8b7d7c1 commit a3a0326
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/injectable/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export interface DiContainerForInjection {
deregister(...injectables: Injectable<any, any, any>[]): void;
context: ContextItem[];
getInstances: GetInstances;
sourceNamespace: string | undefined;
}

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 @@ -482,3 +482,22 @@ di.unoverride(someInjectable);

// given injectable, when unoverridden using injectionToken, typing is ok.
di.unoverride(someStringInjectionToken);

// given keyed singleton with sourceNamespace as key, typing is ok
const someKeyedSingletonWithSourceNamespaceAsKey = getInjectable({
id: 'some-keyed-singleton-with-source-namespace-as-key',

instantiate: di => {
expectType<string | undefined>(di.sourceNamespace);

return di.sourceNamespace;
},

lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: di => {
expectType<string | undefined>(di.sourceNamespace);

return di.sourceNamespace;
},
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export default (containerId, { detectCycles = true } = {}) => {
getDi: () => privateDi,
checkForNoMatches,
checkForSideEffects,
getNamespacedId,
});

const decoratedPrivateInject = withInjectionDecorators(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ export const privateInjectFor =
getDi,
checkForNoMatches,
checkForSideEffects,
getNamespacedId,
}) =>
(alias, instantiationParameter, context = []) => {
(alias, instantiationParameter, context = [], source) => {
const di = getDi();

const relatedInjectables = getRelatedInjectables(alias);
Expand All @@ -36,6 +37,8 @@ export const privateInjectFor =
instantiationParameter,
context,
instancesByInjectableMap,
source,
getNamespacedId,
});
};

Expand All @@ -45,6 +48,8 @@ const getInstance = ({
instantiationParameter,
context: oldContext,
instancesByInjectableMap,
source,
getNamespacedId,
}) => {
const newContext = [
...oldContext,
Expand Down Expand Up @@ -97,6 +102,12 @@ const getInstance = ({
source: injectableToBeInstantiated,
});
},

get sourceNamespace() {
return (
getNamespacedId(source).split(':').slice(0, -1).join(':') || undefined
);
},
};

const instanceKey = injectableToBeInstantiated.lifecycle.getInstanceKey(
Expand Down
173 changes: 173 additions & 0 deletions packages/injectable/core/src/scenarios/access-to-namespace.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import getInjectable from '../getInjectable/getInjectable';
import createContainer from '../dependency-injection-container/createContainer';
import lifecycleEnum from '../dependency-injection-container/lifecycleEnum';
import { getInjectionToken } from '@ogre-tools/injectable';

describe('access-to-namespace', () => {
it('given keyed singleton using source namespace as the key, when injected from different scopes, injected instances are scope-specific', () => {
const di = createContainer('irrelevant');

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

const someSourceNamespaceSpecificInjectable = getInjectable({
id: 'some-source-namespace-specific',
instantiate: di => message =>
di.sourceNamespace ? `${di.sourceNamespace}/${message}` : message,

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

injectionToken: someSourceNamespaceSpecificInjectionToken,
});

const registerInjectableInScope1Injectable = getInjectable({
id: 'scope-1',
instantiate: di => injectable => di.register(injectable),
scope: true,
});

const someFunctionalityInScope1Injectable = getInjectable({
id: 'some-functionality-in-scope-1',
instantiate: di => di.inject(someSourceNamespaceSpecificInjectionToken),
});

const registerInjectableInScope2Injectable = getInjectable({
id: 'scope-2',
instantiate: di => injectable => di.register(injectable),
scope: true,
});

const someFunctionalityInScope2Injectable = getInjectable({
id: 'register-injectable-in-scope-2',
instantiate: di => di.inject(someSourceNamespaceSpecificInjectionToken),
});

const someFunctionalityInRootScopeInjectable = getInjectable({
id: 'some-functionality-in-root-scope',
instantiate: di => di.inject(someSourceNamespaceSpecificInjectionToken),
});

di.register(
registerInjectableInScope1Injectable,
registerInjectableInScope2Injectable,
);

const registerInjectableInScope1 = di.inject(
registerInjectableInScope1Injectable,
);

const registerInjectableInScope2 = di.inject(
registerInjectableInScope2Injectable,
);

di.register(someSourceNamespaceSpecificInjectable);
registerInjectableInScope1(someFunctionalityInScope1Injectable);
registerInjectableInScope2(someFunctionalityInScope2Injectable);
di.register(someFunctionalityInRootScopeInjectable);

expect([
di.inject(someFunctionalityInScope1Injectable)('some-value'),
di.inject(someFunctionalityInScope2Injectable)('some-other-value'),
di.inject(someFunctionalityInRootScopeInjectable)(
'some-value-without-namespace',
),
]).toEqual([
'scope-1/some-value',
'scope-2/some-other-value',
'some-value-without-namespace',
]);
});

it('given keyed singleton using source namespace as the key, when injected from nested scopes, injected instances are scope-specific', () => {
const di = createContainer('irrelevant');

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

const someSourceNamespaceSpecificInjectable = getInjectable({
id: 'some-source-namespace-specific',

instantiate: di => message => `${di.sourceNamespace}/${message}`,

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

injectionToken: someSourceNamespaceSpecificInjectionToken,
});

const registerInjectableInScope1Injectable = getInjectable({
id: 'scope-1',
instantiate: di => injectable => di.register(injectable),
scope: true,
});

const registerInjectableInNestedScope1Injectable = getInjectable({
id: 'nested-scope-1',
instantiate: di => injectable => di.register(injectable),
scope: true,
});

const someFunctionalityInNestedScope1Injectable = getInjectable({
id: 'some-functionality-in-scope-1',
instantiate: di => di.inject(someSourceNamespaceSpecificInjectionToken),
});

const registerInjectableInScope2Injectable = getInjectable({
id: 'scope-2',
instantiate: di => injectable => di.register(injectable),
scope: true,
});

const registerInjectableInNestedScope2Injectable = getInjectable({
id: 'nested-scope-2',
instantiate: di => injectable => di.register(injectable),
scope: true,
});

const someFunctionalityInNestedScope2Injectable = getInjectable({
id: 'register-injectable-in-scope-2',
instantiate: di => di.inject(someSourceNamespaceSpecificInjectionToken),
});

di.register(
registerInjectableInScope1Injectable,
registerInjectableInScope2Injectable,
);

const registerInjectableInScope1 = di.inject(
registerInjectableInScope1Injectable,
);

const registerInjectableInScope2 = di.inject(
registerInjectableInScope2Injectable,
);

registerInjectableInScope1(registerInjectableInNestedScope1Injectable);
registerInjectableInScope2(registerInjectableInNestedScope2Injectable);

const registerInjectableInNestedScope1 = di.inject(
registerInjectableInNestedScope1Injectable,
);

const registerInjectableInNestedScope2 = di.inject(
registerInjectableInNestedScope2Injectable,
);

di.register(someSourceNamespaceSpecificInjectable);
registerInjectableInNestedScope1(someFunctionalityInNestedScope1Injectable);
registerInjectableInNestedScope2(someFunctionalityInNestedScope2Injectable);

expect([
di.inject(someFunctionalityInNestedScope1Injectable)('some-value'),
di.inject(someFunctionalityInNestedScope2Injectable)('some-other-value'),
]).toEqual([
'scope-1:nested-scope-1/some-value',
'scope-2:nested-scope-2/some-other-value',
]);
});
});

0 comments on commit a3a0326

Please sign in to comment.