{"id":23708541,"url":"https://github.com/mattriley/node-module-composer","last_synced_at":"2025-10-05T19:19:11.404Z","repository":{"id":41155621,"uuid":"277397962","full_name":"mattriley/node-module-composer","owner":"mattriley","description":"Bring order to chaos. Level up your JS application architecture with Module Composer, a tiny but powerful module composition utility based on functional dependency injection.","archived":false,"fork":false,"pushed_at":"2025-09-20T02:00:45.000Z","size":2108,"stargazers_count":8,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-20T03:33:45.006Z","etag":null,"topics":["application-architecture","browser","closures","composition-root","dependency-injection","dependency-inversion","ejectable","fast","fitness-functions","functional-dependency-injection","higher-order-functions","javascript","lightweight","mermaid","modular-design","module-composition","nodejs","npm-package","software-design","stateful-functions"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mattriley.png","metadata":{"files":{"readme":"README-TEMPLATE.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-07-05T23:06:52.000Z","updated_at":"2025-09-20T02:00:45.000Z","dependencies_parsed_at":"2024-06-19T02:42:28.353Z","dependency_job_id":"7d6f59ca-9838-4fec-8ab6-96a513302de1","html_url":"https://github.com/mattriley/node-module-composer","commit_stats":{"total_commits":536,"total_committers":3,"mean_commits":"178.66666666666666","dds":"0.26679104477611937","last_synced_commit":"d83221a027336f49a0d03384222b9d3b298c0972"},"previous_names":[],"tags_count":117,"template":false,"template_full_name":null,"purl":"pkg:github/mattriley/node-module-composer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattriley%2Fnode-module-composer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattriley%2Fnode-module-composer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattriley%2Fnode-module-composer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattriley%2Fnode-module-composer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattriley","download_url":"https://codeload.github.com/mattriley/node-module-composer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattriley%2Fnode-module-composer/sbom","scorecard":{"id":627951,"data":{"date":"2025-08-11","repo":{"name":"github.com/mattriley/node-module-composer","commit":"802652e63060955d09b369b8a7974fd38a7b3f72"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":1,"reason":"9 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-ghr5-ch3p-vcr6","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T07:00:42.804Z","repository_id":41155621,"created_at":"2025-08-21T07:00:42.804Z","updated_at":"2025-08-21T07:00:42.804Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278505202,"owners_count":25998088,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["application-architecture","browser","closures","composition-root","dependency-injection","dependency-inversion","ejectable","fast","fitness-functions","functional-dependency-injection","higher-order-functions","javascript","lightweight","mermaid","modular-design","module-composition","nodejs","npm-package","software-design","stateful-functions"],"created_at":"2024-12-30T18:00:52.898Z","updated_at":"2025-10-05T19:19:11.375Z","avatar_url":"https://github.com/mattriley.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c%- lib.renderOpening() %\u003e\n\n## Install\n\n\u003c%- await lib.renderCode('npm install module-composer', 'sh', 'https://www.npmjs.com/package/module-composer') %\u003e\n\n## At a glance\n\nA contrived example to set the scene.\n\n```js\n// compose.js\nimport composer from 'module-composer';\nimport modules from './modules/index.js';\n\nexport default ({ config }) =\u003e {\n    const { compose } = composer(modules, { config });\n    const { repositories } = compose('repositories');\n    const { services } = compose('services', { repositories });\n    const { views } = compose('views', { services });\n    return compose.modules;\n}\n```\n\n```js\n// app.js\nimport compose from './compose.js';\nconst { views } = compose();\nviews.welcome.render(); \n```\n\n# API Reference\n\n## Import\n\n```js\nimport composer from 'module-composer'; // 👀 esm\nconst composer = require('module-composer'); // 👀 cjs\n```\n\n## Using options\n\nThe last argument of both `composer` and `compose` take options that customise the composition process. Those options may be specified and overridden according to the following rules:\n\n1. Top-level `composer` options apply to all modules by default.\n2. The top-level `composer` option, `defaults`, takes an object of `compose` options keyed by module name which override (1) options for said module.\n3. `compose` options apply only the module being composed and override both (1) and (2) for said module.\n\nThe following example illustrates with a fictional option:\n\n```js\nconst { compose } = composer(modules, { foobar: 1, defaults: { mod: { foobar: 2 } } });\nconst { mod } = compose('mod', {}, { foobar: 3 });\n```\n\n## Composing modules\n\n### `compose.make` or just `compose`: Compose a module\n\nFor the majority of cases.\n\n```js\nconst modules = {\n    mod1: {\n        fun1: () =\u003e () =\u003e 'hello world'\n    },\n    mod2: {\n        fun2: ({ mod1 }) =\u003e () =\u003e mod1.fun1()\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod1 } = compose('mod1');\nconst { mod2 } = compose('mod2', { mod1 });\nmod2.fun2(); // == \"hello world\"\n```\n\n### `compose.deep`: Compose a deep module\n\nFor modules that have organisational substructures.\n\n```js\nconst modules = {\n    mod1: {\n        sub1: { // 👀 organisational substructure\n            fun1: () =\u003e () =\u003e 'hello world'\n        }\n    },\n    mod2: {\n        sub2: { // 👀 organisational substructure\n            fun2: ({ mod1 }) =\u003e () =\u003e mod1.sub1.fun1();\n        }\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod1 } = compose.deep('mod1');\nconst { mod2 } = compose.deep('mod2', { mod1 });\nmod2.sub2.fun2(); // == \"hello world\" 👀 sub2 remains\n```\n\n### `compose.flat`: Compose and flatten a module\n\nFor modules that have organisational substructures for development convenience that should be stripped from (flattened in) the final result.\n\n```js\nconst modules = {\n    mod1: {\n        sub1: { // 👀 organisational substructure\n            fun1: () =\u003e () =\u003e 'hello world'\n        }\n    },\n    mod2: {\n        sub2: { // 👀 organisational substructure\n            fun2: ({ mod1 }) =\u003e () =\u003e mod1.fun1();\n        }\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod1 } = compose.flat('mod1');\nconst { mod2 } = compose.flat('mod2', { mod1 });\nmod2.fun2(); // == \"hello world\" 👀 sub2 removed\n```\n\n### `compose.asis`: Register an existing module\n\nFor modules that don't require dependencies.\n\n```js\nconst modules = {\n    mod1: {\n        fun1: () =\u003e 'hello world' // 👀 no higher order function to accept deps\n    },\n    mod2: {\n        fun2: ({ mod1 }) =\u003e () =\u003e mod1.fun1()\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod1 } = compose.asis('mod1'); // 👀 no deps accepted\nconst { mod2 } = compose('mod2', { mod1 });\nmod2.fun2(); // == \"hello world\"\n```\n\n### Nested modules\n\nModules that have an organisational superstructure can be composed by specifying the path (delimited by dot) to the module.\n\n```js\nconst modules = {\n    sup1: {  // 👀 organisational superstructure\n        mod1: {\n            fun1: () =\u003e () =\u003e 'hello world'\n        }\n    },\n    sup2: { // 👀 organisational superstructure\n        mod2: {\n            fun2: ({ mod1 }) =\u003e () =\u003e mod1.fun1();\n        }\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod1 } = compose('sup1.mod1'); // 👀 delimited by dot\nconst { mod2 } = compose('sup2.mod2', { mod1 });  // 👀 delimited by dot\nmod2.fun2(); // == \"hello world\" 👀 sup2 removed\n```\n\n### Retrieving modules\n\nFor convenience, `compose` returns all previous modules composed, in addition to the most recent.\n\n```js\nconst { compose } = composer(modules);\ncompose('mod1');\nconst { mod1, mod2 } = compose('mod2', { mod1 });  // 👀 mod1 also returned\n```\n\nComposed modules can also be accessed directly with `compose.modules`.\n\n```js\nconst { compose } = composer(modules);\ncompose('mod1');\ncompose('mod2', { mod1 }); \nconst { mod1, mod2 } = compose.modules;  // 👀 both mod1 and mod2 are returned\n```\n\n## Self referencing\n\nModule functions can reference others functions in the same module either by name, or by the special alias `self`.\n\n### `self`: Refer to the same module\n\n```js\nconst modules = {\n    mod: {\n        fun1: ({ mod }) =\u003e () =\u003e mod.fun2(),\n        fun2: ({ self }) =\u003e () =\u003e self.fun3(), // 👀 self is an alias of mod\n        fun3: () =\u003e () =\u003e 'hello world'\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod } = compose('mod');\nmod.fun1(); // == \"hello world\"\n```\n\n### `here`: Refer to the same level\n\nIn the case of deep modules, `here` is a reference to the current level in the object hierarchy.\n\n```js\nconst modules = {\n    mod: {\n        fun1: ({ here }) =\u003e () =\u003e here.sub.fun2(), // 👀 here is an alias of mod\n        sub: {\n            fun2: ({ here }) =\u003e () =\u003e here.fun3(), // 👀 here is an alias of mod.sub\n            fun3: () =\u003e () =\u003e 'hello world'\n        }\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod } = compose.deep('mod');\nmod.fun1(); // == \"hello world\"\n```\n\n## Overriding modules == Stubbing made easy\n\nWhere dependency injection is not used, it's common practice in JavaScript to stub file imports using testing tools. This approach can lead to brittle tests because the tests become coupled to the physical file location of scripts in addition to the modules they export.\n\nThe `overrides` option can be used to override any part of the module hierarchy. This can be useful for stubbing in tests.\n\nThe top-level `composer` option, `overrides`, takes an object keyed by module name.\n\n```js\nconst modules = {\n    mod: {\n        fun: () =\u003e () =\u003e http.get('https://foobar.net')\n    }\n};\n\nconst overrides = {\n    mod: {\n        fun: () =\u003e 'hello world' // 👀 same function, different result\n    }\n};\n\nconst { compose } = composer(modules, { overrides });\nconst { mod } = compose('mod');\nmod.fun(); // == \"hello world\" 👀 avoids the http request\n```\n\n## Application configuration\n\nModule Composer provides convenient utility functions for managing application configuration.\n\n### `configure.merge` or just `configure`: Merge config objects\n\n`configure.merge`, or just `configure` takes objects, arrays of objects, and functions and merges them in the order specified using [Lodash merge](https://lodash.com/docs#merge). Functions are invoked with the preceeding merged value as an argument, and the result takes the function's place in the merge sequence.  \n\n```js\nimport { configure } from 'module-composer';\nconst defaultConfig = { a: 1 };\nconst userConfig = { b: 2 };\nconst deriveConfig = config =\u003e ({ c: config.a + config.b });\nconst config = configure(defaultConfig, userConfig, deriveConfig);\n// == { a: 1, b: 2, c: 3 }\n```\n\n### `configure.mergeWith`: Custom merge config objects\n\n`configure.mergeWith` applies a customiser function as the first argument using [Lodash mergeWith](https://lodash.com/docs/#mergeWith). The following example demonstrates array concatenation.\n\n```js\nimport { configure } from 'module-composer';\n\nconst customiser = (objValue, srcValue) =\u003e {\n    if (Array.isArray(objValue)) return objValue.concat(srcValue);\n};\n\nconst defaultConfig = { arr: [1] };\nconst userConfig = { arr: [2] };\nconst config = configure.mergeWith(customiser, defaultConfig, userConfig);\n// == { arr: [1, 2] }\n```\n\n### Configuration as an option\n\nModule Composer can also take configuration as an option with the same behaviour as `configure.merge`. This not only returns the resulting configuration but also injects it automatically into each composed module.\n\n```js\nconst modules = {\n    mod: {\n        fun: ({ config }) =\u003e () =\u003e config.c\n    }\n};\n\nconst defaultConfig = { a: 'hello' };\nconst userConfig = { b: 'world' };\nconst deriveConfig = config =\u003e ({ c: `${config.a} ${config.b}` });\nconst { compose, config } = composer(modules, { config: [defaultConfig, userConfig, deriveConfig] });\nconst { mod } = compose('mod'); // 👀 config injected automatically\nmod.fun() // == \"hello world\"\n```\n\nFor added convienience, `defaultConfig` is also an option that will take precedence over `config`.\n\n### Freezing config\n\nTo encourage immutability, configuration is frozen (deeply) to prevent modification. In effect, turning config into constants. This effect can be disabled with the `freezeConfig` option.\n\n#### Frozen config\n\n```js\nconst { compose, config } = composer(modules, { config: { a: 1 } });\nconfig.a = 2; // has no effect\n```\n\n#### Unfrozen config\n\n```js\nconst { compose, config } = composer(modules, { config: { a: 1 }, freezeConfig: false });\nconfig.a = 2; // change is applied\n```\n\n### Config aliases\n\nThe `configAlias` option takes a string or array of string specifying alternative names for config. Config aliases are also injected into each module automatically. By default, config is automatically aliased to `constants`, since config should not change once injected. \n\n#### Default alias\n\n```js\nconst { compose, config, constants } = composer(modules, { config: { a: 1 } });\n// config and constants are the same object reference\n```\n\n#### Custom alias\n\n```js\nconst { compose, config, settings } = composer(modules, { config: { a: 1 }, configAlias: 'settings' });\n// config and settings are the same object reference\n```\n\n\n## Extensions\n\nModule Composer features a number of built-in extensions.\n\nExtensions are enabled by default.\n\nTo selectively enable extensions, import each extension from `module-composer/extensions/`, then import `module-composer/core`:\n\nTaking the `mermaid` extension as an example: \n\n```js\nimport 'module-composer/extensions/mermaid.js';\nimport composer from 'module-composer/core';\n```\n\n### `mermaid`: Generate dependency diagrams\n\nA picture paints a thousand words. There's no better aid for reasoning about software design than a good old-fashioned dependency diagram.\n\nModule Composer supports Mermaid diagrams by generating *Mermaid* diagram-as-code syntax for a given composition.\n\n\u003e Mermaid is a tool for creating diagrams and visualizations using text and code.\u003cbr/\u003e https://mermaid-js.github.io • https://github.com/mermaid-js/mermaid\n\nDid you know that GitHub can render diagrams directly from Mermaid syntax?! See [Include diagrams in your Markdown files with Mermaid](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) for more information.\n\nGiven the following composition:\n\n```js\nimport composer from 'module-composer';\nimport modules from './modules/index.js';\n\nexport default () =\u003e {\n    const { compose } = composer(modules);\n    const { stores } = compose('stores');\n    const { services } = compose('services', { stores });\n    compose('components', { services });\n    return compose.modules;\n};\n```\n\nUse `compose.mermaid()` to generate the following Mermaid diagram-as-code:\n\n```\ngraph TD;\n    components--\u003eservices;\n    services--\u003estores;\n```\n\nWhich Mermaid renders as:\n\n```mermaid\ngraph TD;\n    components--\u003eservices;\n    services--\u003estores;\n```\n\nPretty cool, huh!\n\n### `module-alias`: Reference *modules* by alternative names\n\nAs a `compose` option, `moduleAlias` takes a string or array of string.\n\n```js\nconst modules = {\n    mod1: {\n        fun1: () =\u003e () =\u003e 'hello world',\n    },\n    mod2: {\n        fun2: ({ m1 }) =\u003e () =\u003e m1.fun1() // 👀 m1 is an alias of mod1\n    }\n};\n\nconst { compose } = composer(modules);\nconst { mod1 } = compose('mod1', {}, { moduleAlias: 'm1' });\nconst { m2 } = compose('mod2', { mod1 }, { moduleAlias: 'm2' });\nm2.fun2(); // == 'hello world' 👀 m2 is an alias of mod2\n```\n\n### `function-alias`: Reference *functions* by alternative names\n\nThe `functionAlias` option takes an array of entries specifying patterns and replacements for any matching function.\n\nAs a `compose` option, applies to associated module:\n\n```js\nconst modules = {\n    mod: {\n        fun1: () =\u003e () =\u003e 'hello world',\n        fun2: ({ mod }) =\u003e () =\u003e mod.fn1(), // 👀 fn1 is an alias of fun1\n    } \n};\n\nconst { compose } = composer(modules};\nconst { mod } = compose('mod', { dep1, dep2 }, { functionAlias: [ [/fun/, 'fn'] ] });\nmod.fn2(); // == \"hello world\" 👀 fn2 is an alias of fun2\n```\n\nAs a top-level `composer` option, applies to any module:\n\n```js\nconst modules = {\n    mod1: {\n        fun1: () =\u003e () =\u003e 'hello world'        \n    },\n    mod2: {\n        fun2: ({ mod1 }) =\u003e () =\u003e mod1.fn1(), // 👀 fn1 is an alias of fun1\n    }\n};\n\nconst { compose } = composer(modules, { functionAlias: [ [/fun/, 'fn'] ] }}; // 👀 composer option\nconst { mod1 } = compose('mod1');\nconst { mod2 } = compose('mod2', { mod1 });\nmod2.fn2(); // == \"hello world\" 👀 fn2 is an alias of fun2\n```\n\n### `access-modifiers`: True public and private functions\n\nThe `privatePrefix` and `publicPrefix` options take a string specifying a prefix used to determine whether a function should be considered private or public. By default, these are set to `_` and `$` respectively. The prefixes are stripped from the final result.\n\nTypically only one prefix is required, since any unprefixed functions will assume the opposite. If both prefixes are used, unprefixed default to private.\n\n```js\nconst modules = {\n    foo: {\n        public:   ({ foo }) =\u003e () =\u003e { /* ✅ foo.private */ },\n        _private: ({ foo }) =\u003e () =\u003e { /* ✅ foo.public  */ }\n    },\n    bar: {\n        $public: ({ foo, bar }) =\u003e () =\u003e { /* ❌ foo.private, ✅ bar.private */ },\n        private: ({ foo, bar }) =\u003e () =\u003e { /* ✅ foo.public,  ✅ bar.public  */ }\n    }\n};\n\nconst { compose } = composer(modules);\nconst { foo } = compose('foo');          // ✅ foo.public, ❌ foo.private\nconst { bar } = compose('bar', { foo }); // ✅ bar.public, ❌ bar.private\n```\n\n### `eject`: Opt out of Module Composer\n\nModule Composer can be _ejected_ by generating the equivalent vanilla JavaScript code. Well, that's the vision anyway! The current implementation has some limitations. Please raise an issue if you'd like to see this developed further.\n\n### `perf`: Meaure composition performance\n\nModule Composer is fast. In fact, so fast that it needs to be measured with sub-millisecond precision. Performance is measured by default for easy analysis.\n\nUse `compose.perf()` to see the total composition duration, and a break down of duration per module.\n\n# Why Module Composer?\n\n## Background\n\nWhy is it so common for JavaScript applications these days (backend _and_ frontend) to be organised and reasoned about in terms of scripts and files, and navigated via a convoluted maze of file imports?\n\nModule Composer aims to encourage good modular design and intentionality for application architecture by making it easier to design and reason about applications at a higher level, in this case, as a composition of _modules_.\n\nSo what is a module? Not to be confused with JavaScript CJS or ESM modules, a module in this context is simply a plain old JavaScript object (a POJO!) containing _higher-order_ functions that accept module dependencies as arguments. These higher-order functions in turn, return first-order functions enabling invocation to be deferred to later in the application lifecycle. Module dependencies remain available to the first-order function owing to the power of _closures_ (stateful functions). Closures are a native feature of JavaScript.\n\nThis is analogous to calling a class constructor with dependencies and returning the resulting instance. However rather than using a class to encapsulate dependency state, closure functions are used instead.\n\nIf that sounds like a lot to wrap your head around, fear not! Implementation-wise it's actually rather simple. \n\n## How it works\n\nConsider the following example:\n\n```js\nconst modules = {\n    components: {\n        productDetails: ({ orderingService }) =\u003e ({ product }) =\u003e {\n            // When Add to Cart button clicked...\n            orderingService.addToCart({ product, quantity: 1 });\n        }\n    },\n    orderingService: {\n        addToCart: () =\u003e ({ product, quantity }) =\u003e {\n            ...\n        }\n    }\n};\n```\n\nThe `components` module is a plain-old JavaScript object representing some kind of UI components. \n\nIt contains one entry, `productDetails` which returns a higher-order function accepting the `orderingService` module as a dependency. This dependency would be satisfied early in the application lifecycle, ideally as close to the application entry point, i.e. _main_, as possible. \n\nThe result is a first-order function which in this context could be thought of as the `productDetails` component factory function. It accepts a `product` argument and enables the capability of adding a product to a shopping cart via the `orderingService` module. The entry point is too early in the application lifecycle to be reasoning about products. Therefore it needs to be pushed deeper into the application so that invocation can be deferred until the appropriate moment.\n\nThe following example demonstrates invocation without Module Composer:\n\n```js\n// Program entry point\nimport modules from './modules/index.js'; // as above\nconst components = {}, orderingService = {};\norderingService.addToCart = modules.orderingService.addToCart(); // no dependencies\ncomponents.productDetails = modules.components.productDetails({ orderingService });\n\n// Later in the application lifecycle\nconst product = ...\nconst productDetails = components.productDetails({ product });\n```\n\nAs demonstrated, this handy pattern can be applied in vanilla JavaScript without the use of any tools.\n\nSo why Module Composer?\n\nIt doesn't take long before all the _wiring_ adds up. The wiring follows a consistent pattern an is ripe for automation. And in a nutshell, that's what Module Composer does. \n\nModule Composer simply iterates over an object, invokes each function it finds with the given module dependencies, and returns a _mirror_ of the object with the higher-order functions substituted with the first-order functions. Module Composer is very simple. Is _not_ an IoC container; it does _not_ feature dependency resolution. It is a simple tool that facilitates _Pure DI_. See more on [Dependency Injection](#dependency-injection) below.\n\nHere's the equivalent using Module Composer:\n\n```js\nimport composer from 'module-composer';\nimport modules from './modules/index.js';\nconst { compose } = composer(modules);\nconst { orderingService } = compose('orderingService');\nconst { components } = compose('components', { services });\n```\n\nModule Composer takes care of injecting dependencies into each individual function, cleaning up the code and shifting focus to the composition of modules.\n\np.s. In case you're wondering, Module Composer works with React. Say hello to dependency injection in React, and farewell and good riddance to prop-drilling, context, custom hooks, attemping to work around that lack of it.\n\nSee [Stazione Simulation](https://github.com/mattriley/stazione-simulation) for example usage of Module Composer with React.\n\n## Composition root\n\n\u003e A Composition Root is a (preferably) unique location in an application where modules are composed together.\u003cbr/\u003e— [Mark Seeman](https://blog.ploeh.dk/2011/07/28/CompositionRoot/)\n\nModule Composer is a tool that facilitates module composition, therefore its use should be limited and isolated to the Composition Root, as close to the application entry point as possible.\n\nIn the following example, the Composition Root has been extracted to a separate file, then consumed by the application entry point:\n\n```js\nimport composer from 'module-composer';\nimport modules from './modules/index.js';\n\nexport default () =\u003e {\n    const { compose } = composer(modules);\n    const { orderingService } = compose('orderingService');\n    compose('components', { orderingService });\n    return compose.modules;\n};\n```\n\nExample of an entry point for a SPA:\n\n```js\nimport compose from './compose.js';\nconst { components } = compose(); // Invoke the Composition Root\nconst app = components.app(); // Create an instance of the app component\ndocument.getElementById('app').append(app); // Append the app component to the DOM\n```\n\nExtracting the Composition Root can be especially useful for applications that have multiple entry points.\n\nRecommended reading:\n\n- [Composition Root](https://blog.ploeh.dk/2011/07/28/CompositionRoot/) — Mark Seemann\n- [Understanding the Composition Root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/) — Steven van Deursen \u0026 Mark Seemann\n\n## File-per-function\n\nMany applications revolve around a number of rather large, overwhelming files containing many functions. This can happen organically through the pressure of delivery and sometimes by design driven by the ideas of cohesion and ecapsulation, amongst others. While breaking these large files down into smaller ones would seem to be the logical solution, the thought of managing a great number of small files can also seem overwhelming.\n\nModule Composer makes the idea of file-per-function easy when used in conjunction with *barrel rollups*. In JavaScript, a barrel rollup is typically implemented as an `index.js` file that exports every other file (and perhaps sub-directory) in the same directory - an approach most JavaScript developers would already be familiar with.\n\nExample of file-per-function on the file system:\n\n```\nsrc\n└── app.js\n└── compose.js\n└── modules\n    └── index.js\n    └── ordering-service\n        └── index.js\n        └── add-to-cart.js\n    └── components\n        └── index.js\n        └── product-details.js\n```\n\nExample `src/modules/index.js` using ES Modules:\n\n```js\nimport orderingService from './ordering-service/index.js';\nimport components from './components/index.js';\n\nexport default {\n    orderingService,\n    components\n};\n```\n\nExample `src/modules/index.js` using Common JS:\n\n```js\nconst orderingService = require('./ordering-service');\nconst components = require('./components');\n\nmodule.exports = {\n    orderingService,\n    components\n};\n```\n\nThis approach offers a number of additional benefits including:\n\n- Only ever needing to import a file once regardless of the number of usages.\n- Reducing or eliminating the large blocks of import statements typically found at the top of any file.\n- Eliminating any need for path traversal, i.e. `../../../`. Path traversal is a potential code smell due to the risk of inappropriate coupling. Instead, the relationships between each module are explicitly established during at application initialisation time.\n\nThis pattern opens the possibility of generating `index.js` files. This means that not only is each file only ever imported once, import/require statements needn't be manually written at all. The `module-indexgen` package is designed to do just that: https://github.com/mattriley/node-module-indexgen\n\nIt should be noted that Module Composer is not dependent on the file-per-function pattern. How you structure the file system is up to you.\n\n## Dependency injection\n\nModule Composer achieves the equivalent of _dependency injection_ using closures (stateful functions).\n\nWell known advantages of dependency injection include:\n\n- Enables Inversion of Control (IoC). Hollywood principle: Don't call us, we'll call you!\n- Ability to swap implementations, e.g. repositories that integrate with different database engines.\n- Ability to stub/mock/fake dependencies for testing purposes.\n\nA DI Container is a framework to create dependencies and inject them automatically when required.\n\nPlease note, Module Composer is NOT a _DI Container_. A DI Container is a tool that creates dependencies and injects them automatically when required. Module Composer is a utility for making _Pure DI_ easy.\n\nDependency injection is a big (and sometimes controversial) topic and worth being familiar with.\n\nAlthough Module Composer enables dependency injection, remember that the primary aim is to encourage good modular design and intentionality for application architecture.\n\nRecommended reading:\n\n- [Pure DI](https://blog.ploeh.dk/2014/06/10/pure-di/) — Mark Seemann\n- [When to use a DI Container](https://blog.ploeh.dk/2012/11/06/WhentouseaDIContainer/) — Mark Seeman\n- [Partial application is dependency injection](https://blog.ploeh.dk/2017/01/30/partial-application-is-dependency-injection/) — Mark Seemann\n- [DIP in the Wild](https://martinfowler.com/articles/dipInTheWild.html) — Brett L. Schuchert on martinfowler.com\n- [Inversion of Control Containers and the Dependency Injection pattern](https://martinfowler.com/articles/injection.html) — Martin Fowler\n- [Dependency Injection Inversion](https://sites.google.com/site/unclebobconsultingllc/home/articles/dependency-injection-inversion) — Robert C. \"Uncle Bob\" Martin\n- [The Dependency Inversion Principle](https://drive.google.com/file/d/0BwhCYaYDn8EgMjdlMWIzNGUtZTQ0NC00ZjQ5LTkwYzQtZjRhMDRlNTQ3ZGMz/view) — Robert C. \"Uncle Bob\" Martin\n\n## Functional programming\n\nModule Composer is designed with a bias toward _functional programming_.\n\nThe closure-based approach is only possible thanks to JavaScript support for functions as first-class objects. That's not to suggest JavaScript or Module Composer are necessarily functional, but preferencing functions over classes (for instance) may encourage a more functional design. It's entirely possible, and arguably desirable to design JavaScript applications without classes!\n\nAn important consideration in functional design is the segregation of pure and impure functions. When designing modules, be intentional about purity and impurity.\n\n\u003e One very important characteristic of impurity is that it’s inherently contagious. Any function that depends on the execution of an impure function becomes impure as well.\u003cbr/\u003e—[Oleksii Holub](https://tyrrrz.me/blog/pure-impure-segregation-principle)\n\nSee [Fitness functions](#fitness-functions) below to learn how Module Composer can help maintain pure-impure segregation.\n\nRecommended reading:\n\n- [Pure-Impure Segregation Principle](https://tyrrrz.me/blog/pure-impure-segregation-principle) — Oleksii Holub\n\n## Fitness functions\n\nModule Composer can describe the dependency graph to enable _fitness functions_ on coupling.\n\n\u003e An architectural fitness function, as defined in Building Evolutionary Architectures, provides an objective integrity assessment of some architectural characteristics, which may encompass existing verification criteria, such as unit testing, metrics, monitors, and so on.\u003cbr/\u003e— [Thoughtworks](https://www.thoughtworks.com/en-au/radar/techniques/architectural-fitness-function)\n\nInappropriate coupling leads to brittle designs that can be difficult to reason about, difficult to change and difficult to test.\n\nUse `compose.dependencies` to obtain a dependency graph similar to:\n\n```js\n{\n    components: ['services'],\n    services: ['stores'],\n    stores: []\n}\n```\n\nThe examples below leverage `compose.dependencies` to demonstrate fitness function in the form of unit tests.\n\n### Example 1: N-tier architecture\n\nAssuming an _n-tier_ architecture, where the `components` module resides in the _presentation_ layer, `services` in the _domain_ layer, and `stores` in the _persistence_ layer, it could be tempting to couple `components` to `stores`,  inadvertently bypassing the domain layer.\n\n\u003c%- await lib.renderCode(`\ngraph TD;\n    components[\"components\u003cbr/\u003e(presentation)\"]--\u003e|OK!|services;\n    components--\u003e|NOT OK!|stores;\n    services[\"services\u003cbr/\u003e(domain)\"]--\u003e|OK!|stores;\n    stores[\"stores\u003cbr/\u003e(persistence)\"]\n`, 'mermaid') %\u003e\n\nThe following fitness function asserts that `components` is not coupled to `stores`. \n\n```js\ntest('components is not coupled to stores in order to maintain layering', () =\u003e {\n    const { dependencies } = compose();\n    t.notOk(dependencies['components'].includes('stores'));\n});\n```\n\n### Example 2: Pure-impure segregation\n\n`util` is a module of _pure_ utility functions, and `io` is module is _impure_ io operations. It could be tempting to extend `util` with say file utilities that depend on `io`, however doing so would make `util` impure.\n\n\u003c%- await lib.renderCode(`\ngraph TD;\n    io[\"io\u003cbr/\u003e(impure)\"]--\u003e|OK!|util\n    util[\"util\u003cbr/\u003e(pure)\"]--\u003e|NOT OK!|io\n`, 'mermaid') %\u003e\n\nThe following fitness function asserts that `util` is not coupled to `io`.\n\n```js\ntest('util is not coupled to io in order to maintain purity', () =\u003e {\n    const { dependencies } = compose();\n    t.notOk(dependencies['util'].includes('io'));\n});\n```\n\nThe solution introducing file utilities whilst maintaining purity would be to introduce a new module, say `fileUtil`:\n\n\u003c%- await lib.renderCode(`\ngraph TD;\n    io[\"io\u003cbr/\u003e(impure)\"]--\u003e|OK!|util\n    util[\"util\u003cbr/\u003e(pure)\"]--\u003e|NOT OK!|io\n    fileUtil[\"fileUtil\u003cbr/\u003e(impure)\"]--\u003e|OK!|io\n`, 'mermaid') %\u003e\n\n# Advanced example: Agile Avatars\n\n\u003e Great looking avatars for your agile board and experiment in FRAMEWORK-LESS, vanilla JavaScript.\u003cbr/\u003e\nhttps://agileavatars.com • https://github.com/mattriley/agile-avatars\n\n#### Composition root\n\n\u003c%- await lib.renderCode(lib.fetchCode('src/compose.js', '../agile-avatars', 'https://github.com/mattriley/agile-avatars/tree/master')) %\u003e\n\n#### Performance measurements generated with `perf` extension\n\n\u003c%- process.env.COMPUTER_HARDWARE %\u003e\n\n\u003c%- await lib.compose(modules =\u003e lib.renderCode(lib.json(modules.composition.extensions.perf), 'js'), '../agile-avatars/src/compose.js') %\u003e\n\n#### Mermaid diagram generated with `mermaid` extension\n\n\u003c%- await lib.compose(modules =\u003e lib.renderCode(modules.composition.mermaid(), 'mermaid'), '../agile-avatars/src/compose.js') %\u003e\n\n\u003c%- await lib.compose(modules =\u003e lib.renderCode(modules.composition.mermaid(), ''), '../agile-avatars/src/compose.js') %\u003e\n\n#### Code generated with `eject` extension\n\n\u003c%- await lib.compose(modules =\u003e lib.renderCode(modules.composition.eject(), 'js'), '../agile-avatars/src/compose.js') %\u003e\n\n## Contribution / Design principles\n\n- Vanilla and non-intrusive. Structures passed to Module Composer should have no knowledge of / no dependency on Module Composer.\n- Bundler friendly. Dependencies should be fine grained to ensure minimum bundle size. Don't rely on tree-shaking.\n- Any non-core features should be implemented as an optional extension.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattriley%2Fnode-module-composer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattriley%2Fnode-module-composer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattriley%2Fnode-module-composer/lists"}