{"_id":"@bluealba/rules-js","_rev":"5-723d71c8b4d2d5de8f3c621ed8c36607","name":"@bluealba/rules-js","dist-tags":{"latest":"1.0.0"},"versions":{"0.2.0":{"name":"@bluealba/rules-js","version":"0.2.0","description":"A simple rule-engine for javascript","main":"index.js","author":{"name":"Claudio Fernandez","email":"claudio.fernandez@bluealba.com"},"license":"ISC","scripts":{"test":"eslint '.' && nyc --reporter=html --reporter=text mocha \"test/**/*.test.js\"","lint":"eslint '.'","coverage":"nyc report --reporter=text-lcov | coveralls"},"devDependencies":{"chai":"^4.1.1","chai-as-promised":"^7.1.1","chai-things":"^0.2.0","coveralls":"^2.13.1","csv-parse":"^1.2.0","eslint":"^4.5.0","markdown-toc":"^1.1.0","mocha":"^3.5.0","moment":"~2.20.1","nyc":"^11.0.3","sinon":"^4.0.0","sinon-chai":"^2.14.0"},"dependencies":{},"repository":{"closure":"git","url":"git+https://github.com/bluealba/rules-js.git"},"gitHead":"bbd12f70b003d3a991ce166b3d0525cc292fd275","bugs":{"url":"https://github.com/bluealba/rules-js/issues"},"homepage":"https://github.com/bluealba/rules-js#readme","_id":"@bluealba/rules-js@0.2.0","_npmVersion":"6.4.1","_nodeVersion":"11.2.0","_npmUser":{"name":"jabsatz","email":"julianabsatz@gmail.com"},"dist":{"integrity":"sha512-8S3fAsGSQt4wEvAKLvrXVpKFrMpclUIAWJZ5wcxpdJU91MYvvFTDKbJL2P4PYQ0kT28otPrSafGNHRP7E9TK/w==","shasum":"cc35217080ca245adc8ddd380adef222a11d22c7","tarball":"https://registry.npmjs.org/@bluealba/rules-js/-/rules-js-0.2.0.tgz","fileCount":29,"unpackedSize":139179,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcCn5nCRA9TVsSAnZWagAAOmEP/ipjL0pN9+VEQWN7npa7\nHKJ6PQUAAbWPdnQPtjTtrKMIHs7skVF6zJY2DrSDZOb97okGo3pg7beSInS6\nza4/IgHkUTz3HTJ3XUQr2RYGUDTFOwlZTdsRyPdlw1UtsUJOjSay6/efmu7W\nRMwb2HrGOgxmmlJRSkStUxoRLwm1cKeIx7JY2CaHal+s7AqXeoiXSIiM35L3\nNXNgVaIXDw50hH9uWUPfR8rvmEZ9gMsCKH1YjwNAY0FRloImnTxkMIus14F2\n1cnycFR6t/Uo2LZh4sag6U5Y/OBuVzDNWiS483m5hNFnjDPCQExUG7fzEVZo\nfdDtc1pwrJj81qPTUY7WWpRMibioMVQitbpQTMp8YMFdCGikeCGLxYO6B25K\n9qV/1Zm+Nu/ntzYYNeURQ/MZ0Cik7c3Z3z4dTJ5Bs2PdoGTgatPHY7kOsYMW\njUxDTrwSWwkQIj0FT4vCens8yZM3IEABRUBNtVRc3ewdkRNgm01zsGNu9AFB\nGzgvwKhwUGSFnYDf4yKbTBbtC6UlFXiUzntES8Nip2jq8fTndC0jlRtENSmq\nCFcyz3QTB1jxQjinLMrXnrTKltOcoI+0pIjrGt4AV/6VCKLUxBmUdiMZIuHk\na94Dei346qfpbch25QFeiEaYokkuP5UAo0dZv2C7Z/oUnwjGHNBDC/L7u3or\n+KJa\r\n=QZ1H\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCwPwz5/Zj58eZQk9Ppa46WDz+iZc+XaTRSCZWHkEBvZQIhAKDUXRxuDzdkd8Ki01YdLfIS5UNSWuh3ke4ROucGiGIa"}]},"maintainers":[{"name":"aitrusgit","email":"claudio.fernandez@bluealba.com"},{"name":"jabsatz","email":"julianabsatz@gmail.com"},{"name":"pablote","email":"pablocompagni@gmail.com"},{"name":"gunthers","email":"gunthers@gmail.com"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/rules-js_0.2.0_1544191591260_0.599316789198231"},"_hasShrinkwrap":false},"1.0.0":{"name":"@bluealba/rules-js","version":"1.0.0","description":"A simple rule-engine for javascript","main":"index.js","author":{"name":"Claudio Fernandez","email":"claudio.fernandez@bluealba.com"},"license":"ISC","scripts":{"test":"eslint '.' && jest","lint":"eslint '.'","coverage":"jest --coverage && cat ./tests/coverage/lcov.info | coveralls"},"dependencies":{"moment":"~2.24.0"},"devDependencies":{"chai":"^4.1.1","chai-as-promised":"^7.1.1","chai-things":"^0.2.0","coveralls":"^3.0.3","eslint":"^5.15.3","jest":"^24.5.0","sinon":"^7.3.0","sinon-chai":"^3.3.0"},"jest":{"collectCoverage":true},"repository":{"closure":"git","url":"https://github.com/bluealba/rules-js"},"licenseText":"Internet Systems Consortium license\n===================================\n\nCopyright (c) 2017, Blue Alba\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith or without fee is hereby granted, provided that the above copyright notice\nand this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.","_id":"@bluealba/rules-js@1.0.0","dist":{"shasum":"6ee1bd71d3f8c9719045182829106aa13bf096cb","integrity":"sha512-ZR4bfeTsmXrZHGJiCRQIKNVDhSkmfnHbeYKgbZFPxFw+NWDyWHf/M5VqEKIuQdLP7Yg+7pB8PEBHo5RPwX8Z4A==","tarball":"https://registry.npmjs.org/@bluealba/rules-js/-/rules-js-1.0.0.tgz","fileCount":36,"unpackedSize":32718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcmDO5CRA9TVsSAnZWagAAiYIQAJ1hwrUGlc7q6Ssqmy0Y\nEo/SM0vue/B64yEGhu+47jVN44b2DSJUmbWLmHrd/l9DwPVwspcyNLcVbY3p\n4K5PnsVg6pNJFllolsAl2DZ8FTmxaIei7FHCv8+2SMu5DvbN/KyW4z6w2yHb\nsDvZk+rtqG2YYq7/JdpyjvUg59BK5p0A7tEDOZwbE4+/q79Dc6WAfnKqUH61\nd5U0UKGtMfocw1EuirgVzbvo6mXyqUd35JKbLZ17F9YixP1mGaCTLzDL5Kbu\n/7ewP+EVW7DsO7HF00qPQw35aEAF5pZ/srLqxsCDq/4oleAHHWRzbmjfGR0O\nlrb3DrsI2TkHN2pGHMO0nSnSppUJQbdcpioUJ5n8NP1kOsUbG6ae1j3EGA03\nVtqL/mS/bbiOBDn+BCsKxhCj8C3Jsawm8CbO8kODmbXiJh/p8vierIUOjpS7\nCIRY8rIrn3sVrF3EFu9X1ZELnmXXeDhPwRsr6+3fLrX2hcBPeBKJX6VRDMh+\nj1rQP7x5LGrE8qMhNSwM1mI2vz6SooH4Gob7iCY+rXB45XlyImK/fSFJLnAN\n4XnRBkjS+n6nrWC0+DfqHyF/5SvA6Vm4O5WuTMGPx823R19wQh2jF+woui55\nyqkbPaKbMpkDrbmSCpct/7UL2o9sCloZ1iRiq6BWHsLGKR+YrC7i4a2kgMRw\nPXhV\r\n=laxs\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAJ3IC6/UyxbKj4fC0P/o8Le/0GKD0ny72IG05ubSi7rAiAZdn5JzMblA3vfSTApPq1xySJMxljzV9CdPvsHr9e5UQ=="}]},"maintainers":[{"name":"aitrusgit","email":"claudio.fernandez@bluealba.com"},{"name":"gunthers","email":"gunthers@gmail.com"},{"name":"jabsatz","email":"julianabsatz@gmail.com"},{"name":"pablote","email":"pablocompagni@gmail.com"}],"_npmUser":{"name":"aitrusgit","email":"claudio.fernandez@bluealba.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/rules-js_1.0.0_1553478584430_0.6772251924047494"},"_hasShrinkwrap":false}},"time":{"created":"2018-12-07T14:06:31.191Z","0.2.0":"2018-12-07T14:06:31.446Z","modified":"2022-04-04T19:44:00.365Z","1.0.0":"2019-03-25T01:49:44.706Z"},"maintainers":[{"email":"gunthers@gmail.com","name":"gunthers"},{"email":"claudio.fernandez@bluealba.com","name":"aitrusgit"},{"email":"julianabsatz@gmail.com","name":"jabsatz"},{"email":"pablocompagni@gmail.com","name":"pablote"},{"email":"info@bluealba.com","name":"bluealba-org"},{"email":"npm@bluealba.com","name":"bluealba"}],"description":"A simple rule-engine for javascript","repository":{"closure":"git","url":"https://github.com/bluealba/rules-js"},"author":{"name":"Claudio Fernandez","email":"claudio.fernandez@bluealba.com"},"license":"ISC","readme":"# Rules.JS\n\n[![Build Status](https://travis-ci.org/bluealba/rules-js.svg?branch=master)](https://travis-ci.org/bluealba/rules-js)\n[![npm](https://img.shields.io/npm/v/rules-js.svg)](https://npmjs.org/package/rules-js)\n[![npm](https://img.shields.io/npm/dt/rules-js.svg)](https://npmjs.org/package/rules-js)\n![David](https://img.shields.io/david/bluealba/rules-js.svg)\n[![Coverage Status](https://coveralls.io/repos/github/bluealba/rules-js/badge.svg?branch=master)](https://coveralls.io/github/bluealba/rules-js?branch=master)\n\n## Overview\n\nThis is an implementation of a very lightweight pure javascript rule engine.\nIt's very loosely inspired on drools, but keeping an extra effort in keeping complexity to the bare minimum.\n\n- [Rules.JS](#rulesjs)\n  * [Overview](#overview)\n    + [Install](#install)\n    + [Usage Example](#usage-example)\n  * [Engine](#engine)\n  * [Fact](#fact)\n  * [Closures](#closures)\n    + [Provided closures](#provided-closures)\n      - [Parameterizable closures](#parameterizable-closures)\n      - [Parameterless closures (syntax sugar)](#parameterless-closures-syntax-sugar)\n    + [Rules](#rules)\n    + [Closure arrays (reduce)](#closure-arrays-reduce)\n    + [Rules flow](#rules-flow)\n\t\t\n### Install\n\n```\nnpm install rules-js\n```\n\n\n### Usage Example\nThis very naive example on how to create an small rule engine to process online orders. This isn't meant to imitate a full business process, neither to shown the full potential of Rules.JS\n\nWe start by defining a rules file in JSON.\n\n```json\n{\n\t\"name\": \"process-orders\",\n\t\"rules\": [\n\t\t {\n\t\t\t \"when\": \"always\",\n\t\t\t \"then\": \"calculateTotalPrice\"\n\t\t },\n\t\t {\n\t\t\t \"when\": { \"closure\": \"checkStockLocation\", \"location\": \"localDeposit\" },\n\t\t\t \"then\": [\n\t\t\t\t { \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 },\n\t\t\t\t \"createDispatchOrder\"\n\t\t\t ]\n\t\t },\n\t\t {\n\t\t\t \"when\": { \"closure\": \"checkStockLocation\", \"location\": \"foreignDeposit\" },\n\t\t\t \"then\": [\n\t\t\t\t \"calculateShipping\",\n\t\t\t\t \"createDispatchOrder\"\n\t\t\t ]\n\t\t },\n\t\t {\n\t\t\t \"when\": { \"closure\": \"checkStockLocation\", \"location\": \"none\" },\n\t\t\t \"then\": { \"closure\": \"error\", \"message\": \"There is availability of such product\"}\n\t\t }\n\t]\n}\n```\n\nNow we can evaluate any order using such rules file. First we create the engine.  We can do it inside a js module:\n\n```javascript\nconst engine = new Engine();\n\n// We are intentionally missing something here:\n// We need first to define what the verbs 'checkStockLocation', 'calculateTaxes'\n// 'calculateShipping' and 'createDispatchOrder' actually mean\n\nconst definitions = require(\"./process-orders.rules.json\");\nengine.add(definitions);\n\nmodule.exports = function (order) {\n\treturn engine.process(\"process-orders\", order);\n}\n```\n\nThen we just use that module to evaluate orders.  Each order is a **fact** that\nwill be provided to our rule engine.\n\n```javascript\nconst orderProcessorEngine = require(\"./order-processor-engine\");\n\nconst order = {\n\tbooks: [\n\t\t{ name: \"Good Omens\", icbn: \"0060853980\", price: 12.25 }\n\t],\n\tclient: \"mr-goodbuyer\"\n};\n\n//result is a Promise since rules might evaluate asynchronically.\norderProcessorEngine(order).then(result => {\n\tconst resultingDispatchOrder = result.fact;\n\t// handle the result in any way\n})\n```\n\nOf course, we intentionally omit defining what this verbs mean: *calculateTotalPrice*,\n*checkStockLocation*, *calculateTaxes*, *calculateShippingAndHandling* and *createDispatchOrder*.  \nThose reference to provided closures and are the place were implementor should provide\ntheir own business code.\n\nThis is where we drift slightly away from the drools-like frameworks take on defining\nhow a rule engine should actually be configured.  We believe that's a good idea\nto define separately the implementation of the business actions that can be executed\nand the logic that tells us when and how they are triggered.\n\nThere are two ways to do define the missing verbs, through functions or objects. Both\nof them are rather similar:\n\n\n```javascript\nengine.closures.add(\"calculateTotalPrice\", (fact, context) => {\n\tfact.totalPrice = fact.books.reduce((total, book) => total + book.price, 0);\n\treturn fact;\n});\n```\n\nor \n\n```javascript\nclass CalculateTotalPrize extends Closure {\n\tprocess(fact, context) {\n\t\tfact.totalPrice = fact.books.reduce((total, book) => total + book.price, 0);\n\t\treturn fact;\n\t} \n}\n\nengine.closures.add(\"calculateTotalPrice\", new CalculateTotalPrize());\n```\n\n## Engine\nThis is the main entry point for Rules.js.  The typically lifecycle of a rule engine implies three steps:\n\n1. Instantiate the rule engine.  This instance will be kept alive during the whole life of the application.\n2. Configure the engine provided closures.\n3. Configure the engine with high-level closures (rules, ruleflows). This is usually done by requiring a rule-flow definition file \n4. Evaluate multiple facts using the engine and obtain a result for each one of them.\n\n```javascript\nconst Engine = require(\"rules-js\");\n\n// 1. instantiate\nconst engine = new Engine();\n\n// 2. configure\nengine.closures.add(\"calculateTotalPrice\", (fact, context)) => {\n\tfact.totalPrice = fact.books.reduce((total, book) => total + book.price, 0);\n\treturn fact;\n});\n\nengine.closures.add(\"calculateTaxes\", (fact, context)) => {\n\tfact.taxes = fact.totalPrice * context.parameters.salesTax;\n\treturn fact;\n}, { required: [\"salesTax\"] });\n\nconst definitions = require(\"./process-orders.rules.json\");\nengine.closures.create(definitions);\n\n// 3. at some time later, evaluate facts using the engine\nmodule.exports = function (fact) {\n\treturn engine.process(\"process-orders\", fact);\n}\n\n```\n\n## Fact\nA fact is an object that is feeded to the rule engine in order to produce a\ncomputational result. Any object can be a fact.\n\n## Closures\nOne of the core concepts of Rules.JS are closures. We defined **closure** as any\ncomputation bound to a certain context that can act over a **fact** and return a\nvalue.\n\n### Provided closures\n\nIn Rules.JS we have a mechanism to tie either a plain old javascript function or\nan object that extends the `Closure` class to a certain\nname. These are provided closures can be later referenced by any other piece of\nthe rule engine (*rules*, *ruleFlows*) hence becoming the foundational stones of\nthe library.\n\n```javascript\n// a simple closure implementation function\nfunction (fact, context) {\n\treturn fact.totalPrice * context.parameters.salesTax;\n}\n\n//the same thing implemented through a class\nclass TaxCalculator extends Closure {\n\tprocess(fact, context) {\n\t\treturn fact.totalPrice * context.parameters.salesTax;\n\t}\n}\n```\n\n\nNote that any closure will receive two parameters:\n```\n@param      {Object}  fact - the fact is the object that is current being evaluated by the closure.\n@param      {Context} context - the fact's execution context\n@param      {Object}  context.parameters - the execution parameters\n@param      {Engine}  context.engine - the rule engine\n\n@return     {Object}  the result of the computation (this can be a Promise too!)\n```\nThe main parameter is of course the **fact**, closures need to derive their result\nfrom each different fact that is provided. **context.parameters** hash is introduced \nto allow the reuse of closure implementations through parameterization. \n\nClosures will often enhance the current provided fact by adding extra information\nto it. Of course, a closure can always alter the fact.\n\n```javascript\nfunction (fact, context) {\n\tfact.taxes = fact.totalPrice * 0.8;\n\treturn fact;\n}\n```\n\n*Note*: It's a good idea to keep closures stateless and idempotent, however this\nis not a limitation imposed by Rules.JS\n\nWe can register provided closures into a rule engine by invoking the following:\n\n```javascript\nengine.closures.add(\"calculateTaxes\", (fact, context) => {\n\tfact.taxes = fact.totalPrice * 0.08\n\treturn fact;\n});\n```\n\nNotice that in a simplest form the `add` method receive the name\nthat we want the closure to have and the closure implementation function.\n\nWe can later reference to any provided closure (actually, any *named* closure)\nin the JSON rule file through a json object like the following:\n\n```json\n{ \"closure\": \"calculateTaxes\" }\n```\n\n#### Parameterizable closures\n\nWe can add parameters to our closures implementation, that way the same closure\ncode can be reused in different contexts. We can change the former `calculateTaxes`\nto receive the tax percentage.\n\n```javascript\nengine.closures.add(\"calculateTaxes\", (fact, context) => {\n\tfact.taxes = fact.totalPrice * context.parameters.salesTax;\n\treturn fact;\n}, { required: [\"salesTax\"] });\n```\n\nNow every time that a closure is referenced in a rules file we will need to provide\na value for the `salesTax` parameter (otherwise we will get an error while parsing\nit!).\n\n```json\n{ \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 }\n```\n\n#### Parameterless closures (syntax sugar)\nWhen using closures that don't receive any parameters we can, instead of writing\nthe whole closure object `{ \"closure\": calculateShipping\" }` we can simply\nreference it by its name: `\"calculateShipping\"`.\n\n### Rules\nRules an special kind of closures that are composed by two component closures (of\nany kind!). One of the closures will act as a condition (the *when*), conditionating\nthe execution of the second closure (the *then*) to the result of its evaluation.\n\n```json\n{\n\t\"when\": { \"closure\": \"hasStockLocally\" },\n\t\"then\": { \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 }\n}\n```\n\n... which is the same than writing ...\n\n```json\n{\n\t\"when\": \"hasStockLocally\",\n\t\"then\": { \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 }\n}\n```\n\n### Closure arrays (reduce)\nClosures can also be expressed as an array of closures.  When evaluating an\narray of closures Rule.JS will perform a reduction, meaning that the resulting\nobject of each closure will become the fact of the next one.\n\n```json\n[\n\t{ \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 },\n\t{ \"closure\": \"makeCreditCardCharge\" },\n\t{ \"closure\": \"createDispatchOrder\" }\n]\n```\n\nYou can also mix syntaxes inside the array\n\n```json\n[\n\t{ \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 },\n\t\"makeCreditCardCharge\",\n\t\"createDispatchOrder\"\n]\n```\n\nOr even using completely different types of closures (i.e. regular provided closures,\nrules, nested arrays of closures)\n\n```json\n[\n\t{\n\t\t\"when\": \"isTaxAccountable\",\n\t\t\"then\": { \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 }\n\t},\n\t\"makeCreditCardCharge\",\n\t\"createDispatchOrder\"\n]\n```\n\n#### Arrays as conditions\n\nYou can also use closure arrays as conditions. By default they will work with \"and\" (`&&`) logic\n\n```json\n{\n\t\"when\": [\"isFoo\", \"isBar\"],\n\t\"then\": \"executeOrder66\"\n},\n```\n\nYou can also define an \"and\" or \"or\" strategies to apply them.\n\n```json\n{\n\t\"when\": [\"isFoo\", \"isBar\"],\n\t\"conditionStrategy\": \"or\",\n\t\"then\": \"executeOrder66\"\n},\n```\n\nThere is also a \"last\" strategy, which makes it work like a regular reducer closure array.\n\n```json\n{\n\t\"when\": [\"transformForFoo\", \"isFoo\"],\n\t\"conditionStrategy\": \"last\",\n\t\"then\": \"executeOrder66\"\n},\n```\n\n### Rules flow\nA rule flow is a definition of a chain of rules that will be evaluated (and applied)\nin order. Typically this is the higher order construction that is registered into rules js.\n\n```json\n{\n\t\"name\": \"process-orders\",\n\t\"rules\": [\n\t\t {\n\t\t\t \"when\": \"always\",\n\t\t\t \"then\": \"calculateTotalPrice\"\n\t\t },\n\t\t {\n\t\t\t \"when\": { \"closure\": \"checkStockLocation\", \"location\": \"localDeposit\" },\n\t\t\t \"then\": [\n\t\t\t\t { \"closure\": \"calculateTaxes\", \"salesTax\": 0.08 },\n\t\t\t\t \"createDispatchOrder\"\n\t\t\t ]\n\t\t },\n\t\t {\n\t\t\t \"when\": { \"closure\": \"checkStockLocation\", \"location\": \"foreignDeposit\" },\n\t\t\t \"then\": [\n\t\t\t\t \"calculateShipping\",\n\t\t\t\t \"createDispatchOrder\"\n\t\t\t ]\n\t\t },\n\t\t {\n\t\t\t \"when\": { \"closure\": \"checkStockLocation\", \"location\": \"none\" },\n\t\t\t \"then\": { \"closure\": \"error\", \"message\": \"There is availability of such product\"}\n\t\t }\n\t]\n}\n```\n\n \n","readmeFilename":"README.md"}