From 6af7f4cc844142efaff1f9e5a1bc7d1eeb8bfa29 Mon Sep 17 00:00:00 2001 From: Iku-turso Date: Fri, 23 Sep 2022 15:20:36 +0200 Subject: [PATCH] feat: Make auto-register also register named exports, and not just the default export --- .../src/autoRegister.js | 38 ++++++--- .../src/autoRegister.test.js | 85 ++++++++++--------- packages/injectable/index.js | 5 +- .../src/getInjectable/getInjectable.js | 3 + 4 files changed, 77 insertions(+), 54 deletions(-) diff --git a/packages/injectable-extension-for-auto-registration/src/autoRegister.js b/packages/injectable-extension-for-auto-registration/src/autoRegister.js index 91d50af6..fbb218e2 100644 --- a/packages/injectable-extension-for-auto-registration/src/autoRegister.js +++ b/packages/injectable-extension-for-auto-registration/src/autoRegister.js @@ -3,6 +3,7 @@ import isString from 'lodash/fp/isString'; import isFunction from 'lodash/fp/isFunction'; import { pipeline } from '@ogre-tools/fp'; import tap from 'lodash/fp/tap'; +import { injectableSymbol } from '@ogre-tools/injectable'; import forEach from 'lodash/fp/forEach'; import flatMap from 'lodash/fp/flatMap'; @@ -11,33 +12,42 @@ const hasInjectableSignature = conforms({ instantiate: isFunction, }); -const getFileNameAndDefaultExport = requireContext => - requireContext.keys().map(key => [key, requireContext(key).default]); +const getFileNameAndModule = requireContext => + requireContext.keys().map(key => [key, requireContext(key)]); const registerInjectableFor = di => - ([, injectable]) => - di.register(injectable); + ([, module]) => { + di.register(...Object.values(module).filter(isInjectable)); + }; -const verifyInjectable = ([fileName, injectable]) => { - if (!injectable) { - throw new Error( - `Tried to register injectable from ${fileName}, but no default export`, - ); - } +const verifyInjectables = ([[fileName, module]]) => { + const injectables = Object.entries(module).filter(([, exported]) => + isInjectable(exported), + ); - if (!hasInjectableSignature(injectable)) { + if (injectables.length === 0) { throw new Error( - `Tried to register injectable from ${fileName}, but default export is of wrong shape`, + `Tried to register injectables from "${fileName}", but there were none"`, ); } + + injectables.forEach(([propertyName, injectable]) => { + if (!hasInjectableSignature(injectable)) { + throw new Error( + `Tried to register injectables from "${fileName}", but export "${propertyName}" is of wrong shape`, + ); + } + }); }; export default ({ di, requireContexts }) => { pipeline( requireContexts, - flatMap(getFileNameAndDefaultExport), - tap(forEach(verifyInjectable)), + flatMap(getFileNameAndModule), + tap(verifyInjectables), forEach(registerInjectableFor(di)), ); }; + +const isInjectable = exported => exported?.aliasType === injectableSymbol; diff --git a/packages/injectable-extension-for-auto-registration/src/autoRegister.test.js b/packages/injectable-extension-for-auto-registration/src/autoRegister.test.js index fc87e040..ef95f4e6 100644 --- a/packages/injectable-extension-for-auto-registration/src/autoRegister.test.js +++ b/packages/injectable-extension-for-auto-registration/src/autoRegister.test.js @@ -16,7 +16,9 @@ describe('autoRegister', () => { const di = createContainer('some-container'); - const someRequireContext = getRequireContextStub(injectableStub); + const someRequireContext = getRequireContextStub({ + default: injectableStub, + }); autoRegister({ di, requireContexts: [someRequireContext] }); @@ -25,10 +27,10 @@ describe('autoRegister', () => { expect(actual).toBe('some-injected-instance'); }); - it('given injectable file with no default export, when auto-registering, throws with name of faulty file', () => { + it('given injectable file with no injectables, when auto-registering, throws', () => { const requireContextStub = Object.assign( () => ({ - notDefault: 'irrelevant', + someExport: 'irrelevant', }), { @@ -41,14 +43,16 @@ describe('autoRegister', () => { expect(() => autoRegister({ di, requireContexts: [requireContextStub] }), ).toThrowError( - 'Tried to register injectable from ./some.injectable.js, but no default export', + 'Tried to register injectables from "./some.injectable.js", but there were none', ); }); - it('given injectable file with default export without id, when auto-registering, throws with name of faulty file', () => { + it('given injectable file with default export without id, when auto-registering, throws', () => { + const injectable = getInjectable({ instantiate: () => {} }); + const requireContextStub = Object.assign( () => ({ - default: 'irrelevant', + default: injectable, }), { keys: () => ['./some.injectable.js'], @@ -60,16 +64,16 @@ describe('autoRegister', () => { expect(() => autoRegister({ di, requireContexts: [requireContextStub] }), ).toThrowError( - 'Tried to register injectable from ./some.injectable.js, but default export is of wrong shape', + 'Tried to register injectables from "./some.injectable.js", but export "default" is of wrong shape', ); }); - it('given injectable file with default export with in but without instantiate, when auto-registering, throws with name of faulty file', () => { + it('given injectable file with an id but without instantiate, when auto-registering, throws', () => { + const injectable = getInjectable({ id: 'irrelevant' }); + const requireContextStub = Object.assign( () => ({ - default: { - id: 'irrelevant', - }, + default: injectable, }), { keys: () => ['./some.injectable.js'], @@ -81,28 +85,8 @@ describe('autoRegister', () => { expect(() => autoRegister({ di, requireContexts: [requireContextStub] }), ).toThrowError( - 'Tried to register injectable from ./some.injectable.js, but default export is of wrong shape', - ); - }); - - it('given injectable file with default export of correct shape, when auto-registering, does not throw', () => { - const requireContextStub = Object.assign( - () => ({ - default: { - id: 'some-injectable-id', - instantiate: () => {}, - }, - }), - { - keys: () => ['./some.injectable.js'], - }, + 'Tried to register injectables from "./some.injectable.js", but export "default" is of wrong shape', ); - - const di = createContainer('some-container'); - - expect(() => - autoRegister({ di, requireContexts: [requireContextStub] }), - ).not.toThrow(); }); it('injects auto-registered injectable with a another auto-registered child-injectable', () => { @@ -122,8 +106,8 @@ describe('autoRegister', () => { di, requireContexts: [ - getRequireContextStub(childInjectable), - getRequireContextStub(parentInjectable), + getRequireContextStub({ default: childInjectable }), + getRequireContextStub({ default: parentInjectable }), ], }); @@ -131,16 +115,39 @@ describe('autoRegister', () => { expect(actual).toBe('some-child-instance'); }); + + it('given file with both injectable and non-injectable exports, given auto-registered, when injecting one of the injectables, does so', () => { + const injectable = getInjectable({ + id: 'some-injectable', + instantiate: () => 'some-instance', + }); + + const di = createContainer('some-container'); + + autoRegister({ + di, + + requireContexts: [ + getRequireContextStub({ + someExport: injectable, + someOtherExport: 'not-an-injectable', + }), + ], + }); + + const actual = di.inject(injectable); + + expect(actual).toBe('some-instance'); + }); }); -const getRequireContextStub = (...injectables) => { +const getRequireContextStub = (...modules) => { const contextDictionary = pipeline( - injectables, - map(injectable => ({ default: injectable })), + modules, - nonCappedMap((file, index) => [ + nonCappedMap((module, index) => [ `stubbed-require-context-key-${index}`, - file, + module, ]), fromPairs, diff --git a/packages/injectable/index.js b/packages/injectable/index.js index 223a6e24..9c8109d0 100644 --- a/packages/injectable/index.js +++ b/packages/injectable/index.js @@ -2,7 +2,9 @@ import getInjectionToken, { injectionTokenSymbol, } from './src/getInjectionToken/getInjectionToken'; -import getInjectable from './src/getInjectable/getInjectable'; +import getInjectable, { + injectableSymbol, +} from './src/getInjectable/getInjectable'; import lifecycleEnum from './src/dependency-injection-container/lifecycleEnum'; import createContainer, { @@ -21,5 +23,6 @@ export { registrationCallbackToken, deregistrationCallbackToken, injectionTokenSymbol, + injectableSymbol, lifecycleEnum, }; diff --git a/packages/injectable/src/getInjectable/getInjectable.js b/packages/injectable/src/getInjectable/getInjectable.js index cf294f1b..c7588569 100644 --- a/packages/injectable/src/getInjectable/getInjectable.js +++ b/packages/injectable/src/getInjectable/getInjectable.js @@ -1,6 +1,9 @@ import lifecycleEnum from '../dependency-injection-container/lifecycleEnum'; +export const injectableSymbol = Symbol('injectable'); + export default ({ lifecycle = lifecycleEnum.singleton, ...injectable }) => ({ + aliasType: injectableSymbol, lifecycle, ...injectable, });