{"_id":"@burgrp/appglue","_rev":"4-e5491bd7acb6dbaeb0a97c13cf3e6083","name":"@burgrp/appglue","dist-tags":{"latest":"1.2.2"},"versions":{"1.1.0":{"name":"@burgrp/appglue","version":"1.1.0","author":{"name":"pb@drake.cz"},"dependencies":{},"main":"src/main.js","license":"CC BY 4.0","gitHead":"64ce661a7f08f9663832454518f6f7c1150ebd66","description":"Simple dependency injection for Node.js.","_id":"@burgrp/appglue@1.1.0","_nodeVersion":"14.17.4","_npmVersion":"6.14.14","dist":{"integrity":"sha512-HgdRsY8K4RCRn4VjsTWm/navwlJ26UU6RnBVqar60OtMjo9taT3tFJrod4PGg3UbtAsh7JbfYgxKczATRvLacg==","shasum":"3c25aae669ca9b53a171f7e36a88f46f370d4486","tarball":"https://registry.npmjs.org/@burgrp/appglue/-/appglue-1.1.0.tgz","fileCount":6,"unpackedSize":11798,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCxavaOZrwZCMzAb/Phsu5KSNA0HNKxEnqD+w0d41wYUwIgddE2eEUC1xNMrY21UHcmK58+6LnvU31pewCZcsYib1g="}]},"_npmUser":{"name":"burgrp","email":"pb@drake.cz"},"directories":{},"maintainers":[{"name":"burgrp","email":"pb@drake.cz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/appglue_1.1.0_1631926316424_0.7461192562251091"},"_hasShrinkwrap":false},"1.2.0":{"name":"@burgrp/appglue","version":"1.2.0","author":{"name":"pb@drake.cz"},"dependencies":{},"main":"src/main.js","license":"CC BY 4.0","gitHead":"fa5c51eed190e9801334fc8e7df7dc654211e3a0","description":"Simple dependency injection for Node.js.","_id":"@burgrp/appglue@1.2.0","_nodeVersion":"14.17.4","_npmVersion":"6.14.14","dist":{"integrity":"sha512-0YPtUL5yEE3iwVwZeuXSAzZVEFF1/48fhqmH4Y+hqfGiec2k2/BytAbIvWUlEIhN/Jxqow+HQ4JjgZUy39L1Kg==","shasum":"5e74e69d65ad95e58f6e635b7d97427889b5c160","tarball":"https://registry.npmjs.org/@burgrp/appglue/-/appglue-1.2.0.tgz","fileCount":6,"unpackedSize":11798,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDxgxNQfm3V78rN/lVvRDfBVIqMCNlYPfhN5q6AIPKwcAIgHwg9P8H2MxBiY6/xvWe46pZqCqCdO2vqJcrokVj2koo="}]},"_npmUser":{"name":"burgrp","email":"pb@drake.cz"},"directories":{},"maintainers":[{"name":"burgrp","email":"pb@drake.cz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/appglue_1.2.0_1631926323162_0.7281271469827724"},"_hasShrinkwrap":false},"1.2.2":{"name":"@burgrp/appglue","version":"1.2.2","author":{"name":"pb@drake.cz"},"dependencies":{},"main":"src/main.js","license":"CC BY 4.0","gitHead":"3b4f1be10f6e64e9af167d84d827ceda02df1340","description":"Simple dependency injection for Node.js.","_id":"@burgrp/appglue@1.2.2","_nodeVersion":"14.18.0","_npmVersion":"6.14.15","dist":{"integrity":"sha512-DX1Dh0KqRbnRCI6l0AmLKoD3aRPbRc2nxrf2p0T+LD9rfDEMVc+BFRvZQsbKT6ZUM2Y7L9zxlnEU329BYVft2w==","shasum":"dc964a0904c31259eb268aefa6b4c157379ce033","tarball":"https://registry.npmjs.org/@burgrp/appglue/-/appglue-1.2.2.tgz","fileCount":6,"unpackedSize":11778,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhzaaECRA9TVsSAnZWagAAJ2IP/j5+UYMrT961NyVj+fxI\nYtvVACgKCvqBVjIYMxkQw2/oHcqT1ch8ONxUlI5IzD3FFLVMIOv+B1B0d2zC\nHdeJjVo3Qa0903Akp+zLUo9TkYW20h/hcOjHs0ub4LktqHMIo5NOBot3Xi6F\njtX2wYL9GrfaGZYrUL768IjD4yiwrRmR9RiBWQBRWk76018vPR4/wtL9F/Lk\ngxeAnGjlz8IFHwhnMOaQMaky0bu2lCruyDgb/He0rejTswVBu8mcFYSu5yAG\njAVbV/QT5kIsLydDRKY9hb4KaNv7pmbDAr1Q3bo/J8pKTV4qI3WdJyvmFHaz\nuJKtSls79o3tAKN2dAY7p0tIB6TnkJH34GqaUVZIH9VNBr1yiapVHrfcvkbo\ni+t1BpVasUWYgS0dadfixa2piSIdPuRND/UBgHH5fFpprNSx2w+ClMFZFHh6\nE9LVigk4AfdcAwSgfKxbRp6vlbLucCLNn4Vsg2gPc9y2Ze2nCwe8Q0Ut1Rjn\nvpcPyZby/D0zIiHnUu+jw7wVbYB4T0wIyxoKqC4aEo5UBmppMV9gOrosYZgJ\no5LfJ5caruH54ufznGlVHxQxTx+8yXg9v627OHlSK/6567Np55SsUU1aZN31\nO0SDCOPfFMPar6DZXR2glfLNvBMxVrak+HbwQ8pnOHxEyS9TAZ5VDgVcp/2S\nhf9r\r\n=aA98\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICb0lGJKDzW7Imck3ynG2tHhmv1dhLfiR86130xg4uCjAiEAoZXXF+01GHeSCqKyMyQ0NDWQNtI6IqWOXU5Z+5iBTMA="}]},"_npmUser":{"name":"burgrp","email":"pb@drake.cz"},"directories":{},"maintainers":[{"name":"burgrp","email":"pb@drake.cz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/appglue_1.2.2_1635235047387_0.11426555171602204"},"_hasShrinkwrap":false}},"time":{"created":"2021-09-18T00:51:56.360Z","1.1.0":"2021-09-18T00:51:56.570Z","modified":"2022-04-04T21:07:26.161Z","1.2.0":"2021-09-18T00:52:03.278Z","1.2.2":"2021-10-26T07:57:27.513Z"},"maintainers":[{"name":"burgrp","email":"pb@drake.cz"}],"description":"Simple dependency injection for Node.js.","author":{"name":"pb@drake.cz"},"license":"CC BY 4.0","readme":"# appglue\nSimple dependency injection for Node.js.\n\n## Purpose\n\nThe appglue library removes typical Node.js hardcoded references caused by `require()` or `import`. This is achieved by \"inverting the control\" in a way, that modules are wired together by simple JSON file. \n\nThe JSON file describes references between modules and may also inject environment variables to further help with application configuration.\n\nAlthough the name of the JSON file may be overridden, we will refer to this file as `config.json` as it is the default name.\n\n## API\n\nThe library takes the `config.json`, resolves all the module references and returns an object which is the application context.\n\nThe context may be returned by asynchronous method `load` e.g.:\n```js\nlet context = await require(\"@burgrp/appglue\")({require}).load();\n```\n\nOr there is handy function `main` to simplify application startup code:\n```js\nrequire(\"@burgrp/appglue\")({require}).main(async context => {\n    // do something with context\n});\n```\n\nThe `{require}` parameter is mandatory. Passing the caller's `require` reference is needed to properly resolve modules.\n\nThere is also an optional parameter `file`, which is the name of configuration JSON. As mentioned above, this defaults to `config.json`. Good practice is to initialize appglue with `{require, file: __dirname + \"/config.json\"}`, which makes the application independent of current working directory.\n\n## Hello world\n\nLet's start with an artificial example - a simple application which consist of two JS modules: `main` and `greeter`. The greeter will export a function `greet(who)`.\n\nThe `config.json` looks like:\n```json\n{\n    \"greeter\": {\n        \"module\": \"./greeter.js\"\n    }\n}\n```\n\nThe top level module `main.js` looks like:\n```js\nrequire(\"@burgrp/appglue\")({require}).main(context => {\n    context.greeter.greet(\"Joe\");\n});\n```\n\nAnd the greeter module `greeter.js` looks like:\n```js\nmodule.exports = context => {\n    return {\n        greet(who) {\n            console.info(`Hello ${who}!`)\n        }\n    }\n}\n```\n\nNote that the greeter module is a factory function, which returns an object. This is appglue idiom. The returned value, in this case the greeter object, is the resolved value inserted into application context. The context parameter is the nested, and already resolved, context inside the module. \n\n#### Module parameters\nIn our simple example the context parameter will be empty, but what about to pass some parameters to greeter?\n\nThen we add `greeting` property to the `config.json`:\n```json\n{\n    \"greeter\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"Hello\"\n    }\n}\n```\n\nAnd use that property in `greeter.js`:\n```js\nmodule.exports = ({greeting}) => {\n    return {\n        greet(who) {\n            console.info(`${greeting} ${who}!`)\n        }\n    }\n}\n```\n\n#### Reusing modules\n\nNow imagine we want to have two greeters, one formal, one informal.\n\nWe would change `config.json` to have two greeters:\n```json\n{\n    \"formal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"Good morning\"\n    },\n    \"informal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"Howdy\"\n    }\n}\n```\n\nAnd then we can refer both in `main.js`:\n```js\nrequire(\"@burgrp/appglue\")({require}).main(({formal, informal}) => {\n    formal.greet(\"Mr. Novak\");\n    informal.greet(\"Joe\");\n});\n```\n\n#### In-context references\n\nOne module may get reference to another part of the context, if it was already resolved (i.e. referenced context must precede the referring context).\n\nFor example, we want to have a new module responsible for writing the string to console. \n\nSo we add the new module to `config.json` and add references:\n```json\n{\n    \"writer\": {\n        \"module\": \"./writer.js\"\n    },\n    \"formal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"Good morning\",\n        \"writer\": \"-> writer\"\n    },\n    \"informal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"Howdy\",\n        \"writer\": \"-> writer\"\n    }\n}\n```\n\nThe new `writer.js` module would look like:\n```js\nmodule.exports = () => {\n    return {\n        write(str) {\n            console.info(str);\n        }\n    }\n}\n```\n\nModified `greeter.js` like:\n```js\nmodule.exports = ({greeting, writer}) => {\n    return {\n        greet(who) {\n            writer.write(`${greeting} ${who}!`)\n        }\n    }\n}\n```\n\nNote that anything behind `->` is normal js code, evaluated in the already resolved context, so it may be more complex expression than just `-> writer`.\n\n#### Environment variables\n\nEnvironment variables are available in evaluation expression (`->`) either directly with `$` prefix, or as map named `$`. This means that e.g. environment variable `HOME` is available by one of two ways:\n- $TEST \n- $.TEST\n\nThe difference is that `$TEST` makes the reference mandatory and initialization will fail, if `TEST` environment variable is undefined. Since `$` is map of all environment variables and is always defined, `$.TEST` resolves to `undefined` but does not fail.\n\nOne could also reference environment variable with default value by `-> $.TEST || 'my-default-value'`.\n\nIn our example, if we want to make both greetings configurable, we change `config.json` to:\n```json\n{\n    \"writer\": {\n        \"module\": \"./writer.js\"\n    },\n    \"formal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"-> $.FORMAL_GREETING || 'Good morning'\",\n        \"writer\": \"-> writer\"\n    },\n    \"informal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"-> $.INFORMAL_GREETING || 'Howdy'\",\n        \"writer\": \"-> writer\"\n    }\n}\n```\n\nThis way we made our example configurable by environment variables without touching JS code itself. We can see application structure, environment references and defaults values on sight.\n\n#### Late references\n\nReferences by `->` are resolved on the first pass which leads to restriction, that only already resolved parts of the context may be resolved. As the context is resolved recursively from top to bottom, one can reference only those parts of the context, which precede the reference. In our example, the reference `-> writer` would not work, if modules are listed in `config.json` in order `formal`, `informal`, `writer`. \n\nTo overcome this restriction, we may use so called late reference. Late reference is prefixed with `=>` instead of `->` and the reference resolves to parameter-less function (aka getter), which returns the reference value, when called.\n\nIf we would need, in our example, to put `writer` behind `formal` and `informal`, our `config.json` looks like:\n```json\n{\n    \"formal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"-> $.FORMAL_GREETING || 'Good morning'\",\n        \"getWriter\": \"=> writer\"\n    },\n    \"informal\": {\n        \"module\": \"./greeter.js\",\n        \"greeting\": \"-> $.INFORMAL_GREETING || 'Howdy'\",\n        \"getWriter\": \"=> writer\"\n    },\n    \"writer\": {\n        \"module\": \"./writer.js\"\n    }\n}\n```\n\nAnd because of late reference we get the getter instead of immediate value, we would need to change `greeter.js` to:\n```js\nmodule.exports = ({greeting, getWriter}) => {\n    return {\n        greet(who) {\n            getWriter().write(`${greeting} ${who}!`)\n        }\n    }\n}\n```\n\n#### \n\n#### Type of the value returned by module function\n\nIn our example modules always return an object with functions. This resembles library style. But module function may return value of any type, including number, string, single function or array.\n\n#### Nested modules\n\nIn our example we had only three modules, defined on the same level. Note that since modules are resolved recursively, they may be nested as needed. Any nested module becomes the owner's module initialization parameter.\n\n#### Mixing constant objects and modules in context\n\nSince the context is JSON structure, modules may be inserted to any level in the structure. The only key to identify the module is the `module` string property. If there is `module` property, the object is resolved as module. If there is no `module` property, the object is passed as-is to the context.\n\n## Await / async\n\nAppglue fully supports asynchronous coding style, so module function may be `async`. Also the function passed to `main(fnc)` function may be `async`.\n\n## License\nLicensed under Creative Commons Attribution 4.0 International (CC BY 4.0).\n","readmeFilename":"README.md"}